公平队列丢失通知

时间:2015-09-01 13:27:58

标签: c++ multithreading stl notifications thread-safety

考虑以下代码

#include <thread>
#include <iostream>
#include <queue>
#include <mutex>
#include <condition_variable>

template <typename T>
class Tqueue 
{
public:

   Tqueue() : m_next_ticket(0),
              m_counter(0) {}

   void push(const T& e){
       std::unique_lock<std::mutex> lock(m_mutex);
       m_queue.push(e);
       lock.unlock();
       m_cond.notify_all();
    };

   T wait_and_pop() {
       std::unique_lock<std::mutex> lock(m_mutex);
       int ticket = m_next_ticket++;
       m_cond.wait(lock,[=]{return (!m_queue.empty())
                  && (ticket == m_counter);});
       m_counter++;
       T data = m_queue.front();
       m_queue.pop();
       return data;
   }

private:
   int m_next_ticket;
   int m_counter;
   std::queue<T> m_queue;
   std::mutex m_mutex;
   std::condition_variable m_cond;   
};

这应该是我提出的公平队列的模板。在这种情况下公平意味着,wait_and_pop()调用以不同线程调用的相同顺序返回。

例如: 线程1 在空队列上调用wait_and_pop()并阻塞。然后线程2 在空队列上调用wait_and_pop()并阻塞。然后线程3 使用push()推送两个事件。现在主题1 应该在主题2 之前返回。

使用以下代码,不时有效。但大多数时候 代码永远阻止:

Tqueue<int> queue;

std::mutex mutex;

void test(int i) 
{
    auto bla = queue.wait_and_pop();
    std::cout << "Thread : "<<bla << std::endl;
}

const int SIZE = 200;

int main(int argc, char *argv[])
{
    std::vector<std::thread> threads;
    for(int i = 0; i < SIZE; ++i)
       threads.push_back(std::thread(test,i));
    for(int i = 0; i < SIZE; ++i)
        queue.push(i);
    for(int i = 0; i < SIZE; ++i)
       threads[i].join();
    return 0;
}

这个想法是为每个线程创建一个唯一的票证。使用条件变量,然后我们在wait_and_pop()函数中等待,直到 插入一个新事件。在push()函数中,新事件被插入队列中,并通知所有等待的线程。每个线程检查是否 如果唯一票证等于当前计数器,则队列不再为空。如果是这样,特定线程将离开条件循环, 从队列中弹出当前事件并增加计数器。

我怀疑,有些通知丢失但是我无法理解这个事实,为什么会这样。任何想法如何解决这个或如何以正确的方式实现这一点?

修改 我按如下方式从队列中更改了代码。现在它似乎工作。 重要的是,我仍然持有锁(在push()和wait_and_pop())。此外,我将票证系统更改为线程ID队列,但这只是一个方便,它使源代码保持紧凑。但我不确定,如果我想使用队列 生产代码,因为我不明白为什么它现在有用,我不知道它是否适用于所有情况。也许有人可以对此发表评论?

template <typename T>
class Tqueue 
{
public:
   void push(const T& e){
       std::unique_lock<std::mutex> lock(m_mutex);
       m_queue.push(e);
       m_cond.notify_all();
   };

   T wait_and_pop() {
       std::unique_lock<std::mutex> lock(m_mutex);
       m_ids.push(std::this_thread::get_id());
       m_cond.wait(lock,[=]{return (!m_queue.empty())
                  && (m_ids.front() == std::this_thread::get_id());});
       T data = m_queue.front();
       m_queue.pop();
       m_ids.pop();
       m_cond.notify_all();
       return data;
   }

private:
   std::queue<T> m_queue;
   std::queue<std::thread::id> m_ids;
   std::mutex m_mutex;
   std::condition_variable m_cond;

 };

2 个答案:

答案 0 :(得分:2)

通知确实丢失了。有些push可能会生成更少数量的线程被唤醒,因为执行m_cond.notify_all();时它只会使等待线程可运行,即准备运行。这些线程仍然需要轮到他们并获取m_cond.wait内的锁。

单个等待线程最终可以执行之前,主线程继续获取互斥锁的次数也是可能的。这导致通知饥饿。

为了使机制有效,您需要在条件受到影响时通知。您已在m_queue.push(e);上通知,这会影响第一个条件!m_queue.empty()。您还需要在wait_and_pop结束时通知,以处理第二个条件ticket == m_counter

T wait_and_pop() {
   ....blah blah
   T data = m_queue.front();
   m_queue.pop();

   lock.unlock();
   m_cond.notify_all();
   return data;
}

注意:it is possible这里我的意思是&#34;最终会有一个最终发生的线程调度&#34; 。我不是说&#34;我不确定&#34;

进一步说明: condition_variable.notify_all()只保证最终唤醒线程。它并不保证X次呼叫将被唤醒X次。此外,由于您的条件,它被减少以保证只通知一个线程,这是根本原因。

关于在wait_and_pop解锁之前或之后通知
无论是在wait_and_pop中释放锁定之前还是之后通知,都不应有任何区别。我指定的修改应该与编辑中的修改相同。我一直在做几个变化的测试(线程计数,等待x线程完成并再次推送),结果相同。

答案 1 :(得分:0)

如果在你开始推送后只启动了你的一个线程,它将永远被阻止。 你应该先尝试队列不为空。如果有数据,为什么还要等。

相关问题