C++并发编程----实现线无锁线程安全的数据结构(《C++ Concurrency in Action》 读书笔记)

Posted 小丑快学习

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++并发编程----实现线无锁线程安全的数据结构(《C++ Concurrency in Action》 读书笔记)相关的知识,希望对你有一定的参考价值。

无锁线程安全的栈

template<typename T>
class lock_free_stack
{
private:
    struct node
    {
        std::shared_ptr<T> data; // 1 指针获取数据 
        node* next;
        node(T const& data_) :
            data(std::make_shared<T>(data_))  
        {}
    };
    std::atomic<node*> head;
public:
	/*
	入栈操作
	*/
    void push(T const& data)
    {
        node* const new_node = new node(data); 
        new_node->next = head.load(); 
        //采用比较-交换技术
        while (!head.compare_exchange_weak(new_node->next, new_node));
    }
	/*
	出栈操作
	*/
	std::shared_ptr<T> pop()
    {
        node* old_head = head.load();
        while (old_head && // 3 在解引用前检查old_head是否为空指针,空代表栈空
            !head.compare_exchange_weak(old_head, old_head->next));
        return old_head ? old_head->data : std::shared_ptr<T>(); //当栈为空时应该返回空指针
    }
};

上述栈的头节点是原子类型的,因此出栈时对于head.compare_exchange_weak(new_node->next, new_node)而言,相当于原子的执行下列操作:

if ( head == new_node->next){
     head = new_node;
     return true;
}
else{
    new_node->next = head;
    return false;
}

也就是说,当没有其他线程修改head的指向时,才能将head指向待插入的节点,倘如此时有其他线程对head进行修改则head == new_node->next将不成立,那么将会重新插入,知道上述的if条件满足条件,节点成功插入,退出循环。

出栈操作则应该检查是否栈为空,如果栈为空,那么将会返回空指针。

节点回收
上述的栈pop操作中,并没有对数据进行删除,因此,这样将会导致内存泄露,但是如果每次在pop操作时就将相应的节点删除,那么有可能导致多个线程删除同一节点,这样必然会导致未定义行为,因此,要将线程安全的亲删除则应该在pop的访问中添加计数器,当其仅有一个线程访问时才能对其数据进行删除,这是我们需要维护一个删除列表,当只有一个线程进行出栈操作时,我们将这个链表说删除。
具体代码的修改如下:

template<typename T>
class lock_free_stack
{
private:
    struct node
    {
        std::shared_ptr<T> data; // 1 指针获取数据 
        node* next;
        node(T const& data_) :
            data(std::make_shared<T>(data_))  
        {}
    };

    std::atomic<node*> head;
    std::atomic<unsigned> threads_in_pop; //记录当前在进行pop()操作的线程
    std::atomic<node*> to_be_deleted; //指向待删除的链表
    void try_reclaim(node* old_head); //尝试去删除该节点的或者整个待删除链表
    

    /*
     删除一个链表
    */
    static void delete_nodes(node* nodes)
    {
        while (nodes)
        {
            node* next = nodes->next;
            delete nodes;
            nodes = next;
        }
    }
    /*
        尝试去删除一个链表和一个节点
    */
    void try_reclaim(node* old_head)
    {
        if (threads_in_pop == 1) //如果当前的访问pop线程只有一个,则尝试去删除该节点和待删除链表
        {
            node* nodes_to_delete = to_be_deleted.exchange(nullptr);
            if (!--threads_in_pop) 
            {
                delete_nodes(nodes_to_delete); 
            }
            else if (nodes_to_delete) //访问上一条语句时如果有线程访问访问pop,则不能去删除一整条链表 
            {
                chain_pending_nodes(nodes_to_delete); // 将链表挂回待删除链表  
            }
            delete old_head; // 因为pop该节点时只有一个线程,因此删除该节点安全
        }
        else//如果多个线程访问pop,则将该节点挂到待删除链表
        {
            chain_pending_node(old_head); 
            --threads_in_pop;
        }
    }
    void chain_pending_nodes(node* nodes)
    {
        node* last = nodes;
        while (node* const next = last->next) // 让last指针指向链表的末尾
        {
            last = next;
        }
        chain_pending_nodes(nodes, last);
    }
    void chain_pending_nodes(node* first, node* last)
    {
        //采用头插法的将整个链表放回到待删除链表,
        //如果插入时有其他的线程操作过,则应该重新插入
        last->next = to_be_deleted;  
        while (!to_be_deleted.compare_exchange_weak(last->next, first));
    }
    void chain_pending_node(node* n)//插入单个节点
    {
        chain_pending_nodes(n, n); 
    }

public:
    void push(T const& data)
    {
        node* const new_node = new node(data); // 2 
        new_node->next = head.load(); // 3 
        while (!head.compare_exchange_weak(new_node->next, new_node));
        // 4
    }

    std::shared_ptr<T> pop()
    {
        ++threads_in_pop; // 2 在做事之前,计数值加1 
        node* old_head = head.load();
        while (old_head &&
            !head.compare_exchange_weak(old_head, old_head->next));
        std::shared_ptr<T> res;
        if (old_head)
        {
            res.swap(old_head->data); // 3 回收删除的节点 
        }
        try_reclaim(old_head); // 4 从节点中直接提取数据,而非拷贝指针 
        return res;
    }
};

测试:

lock_free_stack<int> stack;
void fun1() {
    for (int i = 0; i < 20; i++) {
        stack.push(i);
    }
    for (int i = 0; i < 5; i++) {
        cout << "thread1: " << *stack.pop() << endl;
    }
}

void fun2() {
    for (int i = 0; i < 15; i++) {
        cout << "--thread2: " << *stack.pop() << endl;
    }
}

int main()
{
    thread t1(fun1);
    thread t2(fun2);
    t1.join();
    t2.join();
    return 0;
}

风险指针
维护一个链表,将每个节点和操作他的线程的id绑定,每次检查每个节点指针是否有相应的id和他绑定,如果有指针和其绑定,则不能删除该节点,如果没有id和它绑定,那么可以直接删除该节点。

//风险指针的实现
unsigned const max_hazard_pointers = 100;
struct hazard_pointer
{
    std::atomic<std::thread::id> id;
    std::atomic<void*> pointer;
};
hazard_pointer hazard_pointers[max_hazard_pointers];

/*
    指针拥有者:
    当某个线程创建一个该类的对象,便会自动为该线程寻找一个hazard_pointer类,
   如果不够则抛出异常,当创建该类时会自动将该线程的id设定,并和相应的指针绑定
*/
class hp_owner
{
    hazard_pointer* hp;
public:
    hp_owner(hp_owner const&) = delete;
    hp_owner operator=(hp_owner const&) = delete;
    hp_owner() :
        hp(nullptr)
    {
        for (unsigned i = 0; i < max_hazard_pointers; ++i)
        {
            std::thread::id old_id;
            if (hazard_pointers[i].id.compare_exchange_strong( // 6 尝 
               old_id, std::this_thread::get_id()))
            {
                hp = &hazard_pointers[i];
                break; // 7 
            }
        }
        if (!hp) // 1 
        {
            throw std::runtime_error("No hazard pointers available");
        }
    }
    //返回找到的风险指针的类,并返回该类的指针
    std::atomic<void*>& get_pointer()
    {
        return hp->pointer;
    }
    ~hp_owner() // 2 
    {
        hp->pointer.store(nullptr); // 8 当线程销毁,将指针和数据复原
        hp->id.store(std::thread::id()); // 9 
    }
};
std::atomic<void*>& get_hazard_pointer_for_current_thread() // 
{
    thread_local static hp_owner hazard; // 4 每个线程都有自己的风险指针管理者,知道线程结束
        return hazard.get_pointer(); // 5 
}

//查询当前指针是否在风险指针中
bool outstanding_hazard_pointers_for(void* p)
{
    for (unsigned i = 0; i < max_hazard_pointers; ++i)
    {
        if (hazard_pointers[i].pointer.load() == p)
        {
            return true;
        }
    }
    return false;
}

/*
    删除单个节点
*/
template<typename T>
void do_delete(void* p)
{
    delete static_cast<T*>(p);
}

/*
    删除一个指针指向的节点,仅需要构造一个该类的对象即可
*/
struct data_to_reclaim
{
    void* data;//指向待删除的节点
    std::function<void(void*)> deleter;//删除节点的删除器
    data_to_reclaim* next;//指向下一个节点
    
    template<typename T>
    data_to_reclaim(T* p) : // 1 
        data(p),
        deleter(&do_delete<T>),
        next(0)
    {}

    ~data_to_reclaim()
    {
        deleter(data); // 2 
    }
};

std::atomic<data_to_reclaim*> nodes_to_reclaim;//指向被删除链的头节点

/*
    采用头插法将节点插入到待删除链表中
*/
void add_to_reclaim_list(data_to_reclaim* node) // 
{
    node->next = nodes_to_reclaim.load();
    while (!nodes_to_reclaim.compare_exchange_weak(node -> next, node));
}


//延迟删除节点
template<typename T>
void reclaim_later(T* data) // 4 
{
    add_to_reclaim_list(new data_to_reclaim(data)); // 5 
}


void delete_nodes_with_no_hazards()
{
    //currnet指向待删除的节点
    data_to_reclaim* current = nodes_to_reclaim.exchange(nullptr);

    while (current)
    {
        data_to_reclaim* const next = current->next;
        if (!outstanding_hazard_pointers_for(current->data)) // 7 
        {
            delete current; //将在析构函数中删除
        }
        else
        {   //如果还有线程id和该指针相对应,则不能删除,需要将链表放回到待删除链表中。
            add_to_reclaim_list(current); 
        }
        current = next;
    }
}

使用引用计数的节点

//引用计数计数节点的方式继续删除节点
template<typename T>
class lock_free_stack
{
private:
    struct node;
    struct counted_node_ptr // 含有外部引用计数的节点
    {
        int external_count;
        node* ptr;
    };
    struct node
    {
        std::shared_ptr<T> data;
        std::atomic<int> internal_count; // 内部引用计数节点
        counted_node_ptr next; 
        node(T const& data_) :data(std::make_shared<T>(data_)),internal_count(0)
        {}
    };
    std::atomic<counted_node_ptr> head;  

    //增加外部节点的计数
    void increase_head_count(counted_node_ptr & old_counter)
    {
        counted_node_ptr new_counter;//类似于copy-on-write的修改技术
        do
        {
            new_counter = old_counter;
            ++new_counter.external_count;
        } while (!head.compare_exchange_strong(old_counter, new_counter));

        old_counter.external_count = new_counter.external_count;
    }
public:
    ~lock_free_stack()
    {
        while (pop());
    }
    void push(T const& data) 
    {
        counted_node_ptr new_node;
        new_node.ptr = new node(data);
        new_node.external_count = 1;//插入一个节点时其外部节点仅有头节点指向,内部节点应该为0
        new_node.ptr->next = head.load();
        while (!head.compare_exchange_weak(new_node.ptr -> next, new_node));
    }

    std::shared_ptr<T> pop()
    {
        counted_node_ptr old_head = head.load();
        for (;;)
        {
            increase_head_count(old_head);//增加外部计数的节点
            node* const ptr = old_head.ptr; // 获取指向数据的指针
            if (!ptr)//指针为空,则数据不存在。返回空节点
            {
                return std::shared_ptr<T>();
            }
            if (head.compare_exchange_strong(old_head, ptr->next)) // 如果没有指针修改果头节点则取数据,否则重新获取头节点取数据。
            {
                std::shared_ptr<T> res;
                res.swap(ptr->data); // 4 
                int const count_increase = old_head.external_count - 2; // 

                if (ptr->internal_count.fetch_add(count_increase) == -count_increase)//如果没有其他节点操作该节点则删除该节点
                {
                    delete ptr;
                }
                return res; // 7 
            }
            else if (ptr->internal_count.fetch_sub(1) == 1)//如果当前仅有一个指针指向该节点则删除节点
            {
                delete ptr; // 8 
            }
        }
    }
};

指定内存顺序的无锁栈

template<typename T>
class lock_free_stack
{
private:
    struct node;
    struct counted_node_ptr // 含有外部引用计数的节点
    {
        int external_count;
        node* ptr;
    };
    struct node
    {
        std::shared_ptr<T> data;
        std::atomic<int> internal_count; // 内部引用计数节点
        counted_node_ptr next;
        node(T const& data_) :data(std::make_shared<T>(data_)), internal_count(0)
        {}
    };
    std::atomic<counted_node_ptr> head;

    //增加外部节点的计数
    void increase_head_count(counted_node_ptr& old_counter);
    
public:
    void push(T const & data);
    shared_ptr<T> pop();
    ~lock_free_stack()
    {
        while (pop());
    }
};


template<typename TC++并发编程----无锁实现线程安全队列(《C++ Concurrency in Action》 读书笔记)

JUC并发编程 -- 保护共享资源(加锁实现 & 无锁实现)

java并发编程:管程内存模型无锁并发线程池AQS原理与锁线程安全集合类并发设计模式

java并发编程:管程内存模型无锁并发线程池AQS原理与锁线程安全集合类并发设计模式

JAVA - 并发编程 - 线程安全方案

C++并发编程----利用锁实现线程安全的数据结构(《C++ Concurrency in Action》 读书笔记)