c ++中的细粒度锁定队列

时间:2013-08-01 03:01:27

标签: c++ multithreading concurrency stl

这是Anthony Williams在6.2.3 C ++ Concurrency in Action中引入的细粒度锁定队列。

/*
    pop only need lock head_mutex and a small section of tail_mutex,push only need
    tail_mutex mutex.maximum container concurrency.
*/
template<typename T> class threadsafe_queue
{
    private:
    struct node
    {
        std::shared_ptr<T> data;
        std::unique_ptr<node> next;
    }
    std::mutex head_mutex;   //when change the head lock it.
    std::unique_ptr<node> head;  
    std::mutex tail_mutex;   //when change the tail lock it.
    node* tail;
    std::condition_variable data_cond;

    node* get_tail()
    {
        std::lock_guard<std::mutex> tail_lock(tail_mutex);
        return tail;
    }

    public:
    /* 
        create a dummy node
    */
    threadsafe_queue():
        head(new node),tail(head.get())
    {}

    std::shared_ptr<T> wait_and_pop()
    {
        std::unique_lock<std::mutex> head_lock;
        data_cond.wait(head_lock,[&]{return head.get()!=get_tail();}); //#1
        std::unique_ptr<node> old_head=std::move(head);
        head=std::move(old_head->next);
        return old_head;
    }

    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);
        {
            std::lock_guard<std::mutex> tail_lock(tail_mutex);
            tail->data=new_data;
            node* const new_tail=p.get();
            tail->next=std::move(p);
            tail=new_tail;
        }
        data_cond.notify_one();
    }
}

情况如下:有两个主题(thread1thread2)。 thread1正在执行wait_and_popthread2正在执行push。队列是空的。

thread1位于第2位,已在head.get()!=get_tail()之前检查过data_cond.wait()。此时它的CPU周期已用完。 thread2开始。

thread2完成了push功能并完成了data_cond.notify_one()thread1再次开始。

现在thread1开始data_cond.wait(),但它会永远等待。

这种情况可能会发生吗?如果是这样,如何修复此容器?

2 个答案:

答案 0 :(得分:7)

是的,OP中描述的情况是可能的,并且会导致通知丢失。在谓词函数中注入一个很好的大时间延迟可以很容易地触发。 Here's a demonstration at Coliru。注意程序如何完成10秒(超时的长度为wait_for)而不是100毫秒(生产者在队列中插入项目的时间)。通知丢失。

在条件变量的设计中隐含一个假设条件的状态(谓词的返回值)在关联的互斥锁被锁定时不能改变。对于此队列实现,情况并非如此,因为push可以在不保留head_mutex的情况下更改队列的“空虚”。

§30.5p3指定wait有三个原子部分:

  
      
  1. 释放互斥锁,并进入等待状态;
  2.   
  3. 解除等待;和
  4.   
  5. 重新获取锁定。
  6.   

请注意,这些都没有提到检查谓词,如果有任何传递给waitwait与谓词的行为在§30.5.1p15中描述:

  

效果:     

while (!pred())
      wait(lock);

请注意,这里无法保证谓词检查和wait以原子方式执行。 lock被锁定的前提条件,它是由调用线程保存的相关互斥锁。

至于修复容器以避免丢失通知,我会将其更改为单个互斥实现并完成它。当pushpop最终都采用相同的互斥锁(tail_mutex)时,称它为细粒度锁定有点紧张。

答案 1 :(得分:0)

每次被唤醒时,

data_cond.wait()都会检查条件。因此,即使它已经被检查过,也会在data_cond.notify_one()之后再次检查。此时,有弹出的数据(因为线程2刚刚完成了推送),所以它返回。阅读更多here

你应该担心的唯一问题是当你在一个空队列上调用wait_and_pop然后再从不推送任何数据时。此代码没有用于超时等待并返回错误(或抛出异常)的机制。