【发布时间】: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&。 -
或者可能是
Iterator类型(不在提供的代码中),由std::find_first_of使用 -
@appleapple 如果返回T&,和share_ptr的情况没有区别,因为reference可以直接访问node中的数据。这个例子来自一位著名作家的非常有名的书,所以我试图找到我所缺少的。
-
@myoldgrandpa 我和你的意思一样,这里没有理由返回
shared_ptr。
标签: c++ concurrency thread-safety