为什么get_tail()要使用tail_mutex上的锁?
why should get_tail() use the lock on tail_mutex?
template<typename T>
class threadsafe_queue
{
private:
struct node
{
std::shared_ptr<T> data;
std::unique_ptr<node> next;
};
std::mutex head_mutex;
std::unique_ptr<node> head;
std::mutex tail_mutex;
node* tail;
node* get_tail()
{
std::lock_guard<std::mutex> tail_lock(tail_mutex);
return tail;
}
std::unique_ptr<node> pop_head()
{
std::lock_guard<std::mutex> head_lock(head_mutex);
// is it necessary to use get_tail()
if(head.get()==get_tail())
{
return nullptr;
}
std::unique_ptr<node> const old_head=std::move(head);
head=std::move(old_head->next);
return old_head;
}
public:
threadsafe_queue():
head(new node),tail(head.get())
{}
threadsafe_queue(const threadsafe_queue& other)=delete;
threadsafe_queue& operator=(const threadsafe_queue& other)=delete;
std::shared_ptr<T> try_pop()
{
std::unique_ptr<node> old_head=pop_head();
return old_head?old_head->data:std::shared_ptr<T>();
}
void push(T new_value)
{
std::shared_ptr<T> new_data(
std::make_shared<T>(std::move(new_value)));
std::unique_ptr<node> p(new node);
node* const new_tail=p.get();
std::lock_guard<std::mutex> tail_lock(tail_mutex);
tail->data=new_data;
tail->next=std::move(p);
tail=new_tail;
}
};
以上代码摘自第162页的"c++并发操作"。这里它使用get_tail()
获得锁定在tail_mutex
上的尾部。
书上说:
事实证明,
tail_mutex
上的锁不仅是必要的,以保护尾部本身的读取,但它也是必要的,以确保你不会得到一个数据竞争从头部读取数据。如果没有这个互斥锁,一个线程很可能同时调用try_pop()
和一个线程同时调用push()
,并且它们的操作没有定义顺序。尽管每个成员函数持有一个互斥锁,但它们持有不同互斥锁,并且它们可能访问相同的数据;毕竟,队列中的所有数据都来自对push()
的调用。因为线程可能在没有定义顺序的情况下访问相同的数据,这将是数据竞争和未定义的行为。谢天谢地,get_tail()
中的tail_mutex
锁解决了所有问题。因为对get_tail()
的调用与对push()
的调用锁定了相同的互斥锁,所以这两个调用之间有一个定义的顺序。对get_tail()
的调用要么发生在对push()
的调用之前,在这种情况下,它看到的是tail的旧值,要么发生在对push()
的调用之后,在这种情况下,它看到tail的新值和附加到tail的前一个值的新数据。
我不太明白这一点:如果我只是使用head.get() == tail
,这种比较要么发生在push()
中的tail = new_tail
之前,将head.get()
与tail
的旧值进行比较,要么发生在head.get()
与tail
的新值进行比较之后,为什么会有数据竞争?
我不同意。get_tail
不应该有任何互斥锁,这个函数本身不容易发生数据竞争,也不容易发生内存重排序。事实上,get_tail
应该完全取消。tail的用户应该适当地保护它的使用,但是将互斥锁放在gettail中实际上是一种糟糕的反模式。在每个函数中加入互斥锁当然会使你的程序线程安全。它还将使它成为有效的单线程——如果需要单线程,就不要使用线程。
多线程的艺术不在于到处放互斥锁。
- 如何在没有死锁和/或争用的情况下正确使用 std::mutex C++?
- std::mutex 如何防止线程修改?
- DRD 报告"conflicting load" std::mutex::lock 上的错误
- 是否需要 mutex() 来安全地同时访问具有 2 个线程的数组的不同元素?
- std::atomic 和 std::mutex 的相对性能
- 如何解决"'mutex' in namespace 'std' does not name a type"?
- std::lock_guard 怎么可能比 std::mutex::lock() 更快?
- 当"std::lock_guard<std::mutex>"对象没有名称时的不同行为
- std::mutex::lock() 产生奇怪(和不必要的)ASM 代码
- 使用 std::mutex 保护环路
- std::mutex作为一个成员变量对多个线程来说是安全的吗
- std::shared_timed_mutex何时比std::mutex慢,以及何时(不)使用它
- <mutex> 使用 /clr 或 /clr:pure 编译时不支持
- std::mutex 的发布-获取可见性保证是否仅适用于关键部分?
- 死锁使用 std::mutex 来保护多个线程中的 cout
- 返回持有 std::mutex 锁的 RAII 容器类
- 在 C++11 线程中,std::mutex 对内存可见性有什么保证?
- 如何递归"print list from tail to head"?
- 在任何地方对C++中所有并行线程中的所有锁定和解锁实例使用相同的 std::mutex 和 lock 对象
- 有什么理由C++ 11+ std::mutex 应该声明为全局变量,而不是作为函数参数传递到 std::thread 中