考虑以下代码
#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;
};
答案 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)
如果在你开始推送后只启动了你的一个线程,它将永远被阻止。 你应该先尝试队列不为空。如果有数据,为什么还要等。