【问题标题】:Is this thread safe list using lock is perfect? [closed]这个使用锁的线程安全列表是完美的吗? [关闭]
【发布时间】:2021-06-30 01:09:49
【问题描述】:

下面是使用c++并发锁的线程安全列表示例源代码。

template<typename T>
class threadsafe_list
{
    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:
    threadsafe_list()
    {}
    ~threadsafe_list()
    {
        remove_if([](node const&){return true;});
    }
    threadsafe_list(threadsafe_list const& other)=delete;
    threadsafe_list& operator=(threadsafe_list 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) // (1) is it safe ?
    {
        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);
            }
        }
    }
};

我了解此代码的工作原理。 但我认为这段代码并不完美。 我在争论的地方做了标记。

(1) find_first_if 返回 shared_ptr 而不是 T 的复制值。它提供了并发问题的机会。我会解释更多。 如果用户使用 find_first_if 获取数据的 shared_ptr,那么即使在节点被修改时,用户也可以通过该指针访问数据。这是安全的动作吗?我不这么认为。 我的建议是它应该返回 T 的复制值,这会导致 T find_first_if(Predicate p) {...}。我说的对吗?

编辑: 我将其他问题与原始问题分开,并创建另一个帖子以专注于一个主题

【问题讨论】:

  • 只是我的意见,但容器可能不应该关心这一点。如果访问内容不安全,调用者应将其包装在原子包装器中。
  • 假设,我宁愿返回T&amp;
  • 或者可能是Iterator 类型(不在提供的代码中),由std::find_first_of使用
  • @appleapple 如果返回T&,和share_ptr的情况没有区别,因为reference可以直接访问node中的数据。这个例子来自一位著名作家的非常有名的书,所以我试图找到我所缺少的。
  • @myoldgrandpa 我和你的意思一样,这里没有理由返回shared_ptr

标签: c++ concurrency thread-safety


【解决方案1】:
  1. 其实很有必要,不是防止泄漏而是堆栈溢出。因为节点彼此拥有,每个节点的析构函数会递归调用所有后续节点的析构函数。所以破坏一个长列表很容易炸毁堆栈。

  2. 是的,这是不正确的,您应该查阅Predicate 的文档。更好的是使用 C++14 [](const auto&amp;){return true;},但由于缺少 make_unique,代码可能仅使用 C++11 编写。

  3. 是的,暴露的data 不再受到任何锁的保护。复制可能很昂贵,而仅移动类型呢?同样,这应该在方法中正确记录。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-10
    • 1970-01-01
    • 1970-01-01
    • 2023-04-04
    相关资源
    最近更新 更多