C++并发编程:线程安全链表

问题描述

最近遇到这么一个问题,一个处理路径的函数,在多线程中被调用,我需要记录某个状态的路径,并在后续当再次处于某个状态时,从链表中将其取出。
为了满足这个需求,用来存储的数据结构要满足:

  1. 线程安全:防止在多线程中错误地对一个变量进行修改
  2. 便于查询
  3. 便于插入、删除
    最终想来选择了线程安全的链表,同时备选的队列,但队列不满足便于查询。

工程实践

作为一个“资深”程序员,我已经熟练掌握了从浩如烟海的百度中用CV大法来进行开发的武功。
所以,我很快地搜索到一段代码:

#include <iostream>

#include <memory>
#include <mutex>

template<typename T>   // 模板
class ThreadsafeList
{
    struct node   // 定义链表节点
    {
        std::mutex m;   // 互斥体,用来保证线程安全,锁住节点
        std::shared_ptr<T> data;     // 数据,不必多讲
        std::unique_ptr<node> next;   // 指向下一个节点

        node() : next(){}

        node(T const& value) : data(std::make_shared<T>(value))    // 拷贝构造函数
        {}
    };

    node head;     

public:
    ThreadsafeList()
    {}

    ~ThreadsafeList()
    {
        remove_if([](T const&) {return true; });
    }

    ThreadsafeList(ThreadsafeList const& other) = delete;
    ThreadsafeList& operator=(ThreadsafeList const& other) = delete;

    void push_front(T const& value)      // 在头节点插入函数
    {
        std::unique_ptr<node> new_node(new node(value));     // 创建一个新的节点
        std::lock_guard<std::mutex> lk(head.m);      // 锁
        new_node->next = std::move(head.next);     
        head.next = std::move(new_node);
    }

    template<typename Function>
    void for_each(Function f)  // 遍历函数
    {
        node* current = &head;
        std::unique_lock<std::mutex> lk(head.m);    // 对头节点上锁
        while (node* const next = current->next.get())
        {
            std::unique_lock<std::mutex> next_lk(next->m);   // 上锁
            lk.unlock();                                                 // 对上个节点解锁
            f(*next->data);
            current = next;
            lk = std::move(next_lk);
        }
    }

    template<typename Predicate>
    std::shared_ptr<T> find_first_if(Predicate p)   // 查询函数
    {
        node* current = &head;
        std::unique_lock<std::mutex> lk(head.m);
        while (node* const next = current->next.get())
        {
            std::unique_lock<std::mutex> next_lk(next->m);
            lk.unlock();
            if (p(*next->data))
            {
                return next->data;
            }
            current = next;
            lk = std::move(next_lk);
        }
        return std::shared_ptr<T>();
    }

    template<typename Predicate>
    void remove_if(Predicate p)   // 删除函数
    {
        node* current = &head;
        std::unique_lock<std::mutex> lk(head.m);
        while (node* const next = current->next.get())
        {
            std::unique_lock<std::mutex> next_lk(next->m);
            if (p(*next->data))
            {
                std::unique_ptr<node> old_next = std::move(current->next);
                current->next = std::move(next->next);
                next_lk.unlock();
            }
            else
            {
                lk.unlock();
                current = next;
                lk = std::move(next_lk);
            }
        }
    }
};


int main()
{
    //简单函数功能测试
    ThreadsafeList<int> list;
    list.push_front(1);
    list.for_each([](const int& item) {
        std::cout << item << " ";
        });
    std::cout << std::endl;
    std::shared_ptr<int> ptr=list.find_first_if([](const int& item) {return item == 1; });
    if (ptr.get()!=nullptr) {
        std::cout << *ptr << std::endl;
    }
}

代码分析

在类ThreadSafeList中直接声明了一个结构体变量,比较少见,我也顺手查了一下。

这种方法和在面定义结构体,没什么区别,但是这里需要主义的是,在引用node节点的时候,没有public、private等,所以他是私有的。

class中默认的成员访问权限是private的,而struct中则是public的。

结构体中定义了一个互斥体,这就是保障线程安全的关键所在,每次要对节点进行操作的时候,就先上锁。

 struct node   // 定义链表节点
    {
   
        std::mutex m;   // 互斥体,用来保证线程安全,锁住节点
        std::shared_ptr<T> data;     // 数据,不必多讲
        std::unique_ptr<node> next;   // 指向下一个节点

        node() : next(){
   
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值