为啥 get_tail() 应该使用 tail_mutex 上的锁?
Posted
技术标签:
【中文标题】为啥 get_tail() 应该使用 tail_mutex 上的锁?【英文标题】:why should get_tail() use the lock on tail_mutex?为什么 get_tail() 应该使用 tail_mutex 上的锁? 【发布时间】:2015-10-26 08:37:00 【问题描述】: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++ Concurrency in action”。这里它使用get_tail()
来获取锁定tail_mutex
的尾部。
书上说:
事实证明,
tail_mutex
上的锁定不仅是保护对 tail 本身的读取所必需的,而且还需要确保不会发生从头部读取数据的数据竞争。如果你没有那个互斥锁,一个线程很可能同时调用try_pop()
和一个线程调用push()
,并且它们的操作没有定义的顺序。尽管每个成员函数都持有互斥锁,但它们持有不同互斥锁的锁,并且它们可能访问相同的数据;毕竟,队列中的所有数据都来自对push()
的调用。因为线程可能会在没有定义顺序的情况下访问相同的数据,所以这将是数据竞争和未定义的行为。谢天谢地,get_tail()
中的tail_mutex
的锁定解决了所有问题。因为对get_tail()
的调用与对push()
的调用锁定了相同的互斥锁,所以两个调用之间有一个定义的顺序。对get_tail()
的调用发生在对push()
的调用之前,在这种情况下它会看到tail 的旧值,或者在对push()
的调用之后发生,在这种情况下它会看到tail 的新值和新数据附加到之前的尾部值。
我不太明白:如果我只使用head.get() == tail
,这种比较要么发生在push()
中的tail = new_tail
之前,以将head.get()
与tail
的旧值进行比较,要么在之后比较@ 987654340@ 新值为tail
,为什么会有数据竞争?
【问题讨论】:
if i just use head.get() == tail, this comparison either happens before tail = new_tail in push() to compare head.get() with the old value of tail or after to compare head.get() with the new value of tail, why would there be a data race?
- 这是定义的数据竞争:访问相同的变量 (tail
) 并发 (通过push()
和get_tail()
方法)和一个访问(通过push()
)是写访问。在get_tail()
方法中使用tail_mutex
消除了这种并发性和其他问题,如书中给定的引用所述。
【参考方案1】:
我不同意这一点。 get_tail
里面不应该有任何互斥锁,这个函数本身不存在数据竞争倾向,也不存在内存重新排序倾向。事实上,get_tail
应该被完全删除。 tail 的用户应该适当地保护使用,但是将互斥锁放在 get tail 实际上是一种可怕的反模式。将互斥锁放在每个函数中当然会让你的程序线程安全。它还将使其有效地成为单线程 - 如果需要单线程,请不要使用线程。
多线程的艺术并不在于将互斥锁无处不在。它是在不使用它们。
【讨论】:
感谢提问,迟到了很抱歉以上是关于为啥 get_tail() 应该使用 tail_mutex 上的锁?的主要内容,如果未能解决你的问题,请参考以下文章
为啥 DefaultMessageListenerContainer 不应该使用 CachingConnectionFactory?