为什么get_tail()会使用tail_mutex上的锁?

时间:2015-10-26 08:37:00

标签: c++ multithreading

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;
    }
};

上面的代码摘自&#34; C ++ Concurrency in action&#34;在这里,它使用get_tail()来锁定tail_mutex上的尾部。

这本书说:

  

事实证明,tail_mutex上的锁定不仅是保护尾部读取所必需的,而且还必须确保您不会从头部读取数据。如果你没有那个互斥锁,那么一个线程很可能会同时调用try_pop()和一个调用push()的线程,并且它们的操作没有定义的顺序。即使每个成员函数都锁定互斥锁,它们也会锁定不同的互斥锁,并且它们可能会访问相同的数据;毕竟,队列中的所有数据都来自对push()的调用。因为线程可能在没有定义的顺序的情况下访问相同的数据,所以这将是数据争用和未定义的行为。值得庆幸的是,tail_mutexget_tail()的锁定可以解决所有问题。由于对get_tail()的调用锁定了与push()的调用相同的互斥锁,因此两次调用之间存在已定义的顺序。在调用get_tail()之前调用push(),在这种情况下它会看到tail的旧值,或者在调用push()之后发生,在这种情况下它会看到尾部的新值和附加到前一尾值的新数据。

我不太了解这一点:如果我只使用head.get() == tail,则此比较要么在tail = new_tail push()之前进行,要比较head.get()tail或之后将head.get()tail的新值进行比较,为什么会出现数据竞争?

1 个答案:

答案 0 :(得分:1)

我不同意这一点。 get_tail中不应该包含任何互斥锁,这个函数本身就不容易出现数据争用,也不存在内存重新排序。实际上,应该完全取消get_tail。尾部的用户应该保护使用不合适,但将mutex放入get tail实际上是一个可怕的反模式。当然,将互斥锁放在每个函数中都会使程序线程安全。它还将使其有效地实现单线程 - 如果需要单线程,则不要使用线程。

多线程的艺术不在于将互斥体放在任何地方。它不使用它们。