我已经多次看到以下类型的调度队列实现,其中将新元素推送到队列的线程仅在推送元素之前队列为空时才调用notify_one。这种情况会减少不必要的notify_one调用,因为在推送新元素之前q.size() != 0意味着只有活动线程(假设有多个消费者线程)。
#include <queue>
#include <condition_variable>
#include <mutex>
using Item = int;
std::queue<Item> q;
std::condition_variable cv;
std::mutex m;
void process(Item i){}
void pop() {
while (true) {
std::unique_lock<std::mutex> lock(m);
// This thread releases the lock and start waiting.
// When notified, the thread start trying to re-acquire the lock and exit wait().
// During the attempt (and thus before `pop()`), can another `push()` call acquire the lock?
cv.wait(lock, [&]{return !q.empty();});
auto item = q.front();
q.pop();
process(item);
}
}
void push(Item i) {
std::lock_guard<std::mutex> lock(m);
q.push(i);
if (q.size() == 1) cv.notify_one();
}
int main() { /* ... */ }
但是,以下情况可能吗?假设所有消费者线程都在等待。
- 推送线程获取锁并推送新元素并调用notify_one,因为队列为空。
- 被通知的线程尝试重新获取锁(并退出 wait()),
- 推送线程再次获取锁并在被通知的线程重新获取锁之前推送另一个元素。
在这种情况下,3.之后就不会发生notify_one调用,并且当队列不为空时,只有一个活动线程。
I've seen the following type of a dispatch queue implementation several times where the thread pushing a new element to the queue calls notify_one only when the queue was empty before pushing the element. This condition would reduce unnecessary notify_one calls because q.size() != 0 before pushing a new element means there are only active threads (assuming there are multiple consumer threads).
#include <queue>
#include <condition_variable>
#include <mutex>
using Item = int;
std::queue<Item> q;
std::condition_variable cv;
std::mutex m;
void process(Item i){}
void pop() {
while (true) {
std::unique_lock<std::mutex> lock(m);
// This thread releases the lock and start waiting.
// When notified, the thread start trying to re-acquire the lock and exit wait().
// During the attempt (and thus before `pop()`), can another `push()` call acquire the lock?
cv.wait(lock, [&]{return !q.empty();});
auto item = q.front();
q.pop();
process(item);
}
}
void push(Item i) {
std::lock_guard<std::mutex> lock(m);
q.push(i);
if (q.size() == 1) cv.notify_one();
}
int main() { /* ... */ }
However, is the following scenario possible ? Suppose that all the consumer threads are waiting.
- the pushing thread acquires the lock and push a new element and calls notify_one because the queue was empty.
- a notified thread tries to re-acquire the lock (and exit wait())
- the pushing thread acquires the lock again and pushes another element before a notified thread re-acquires the lock.
In this case, no notify_one call wouldn't occur after 3. and there would be only one active thread when the queue isn't empty.
发布评论
评论(1)
对于唤醒后的操作
根据wake's doc
意味着您的代码在唤醒时在
#1
到#2
之间是原子的。无需担心同步,因为您的编译器应该处理它。对于虚假唤醒
第二个谓词重载将为您处理虚假唤醒。
模板<类谓词> void wait( std::unique_lock& 锁,谓词 stop_waiting ); (2) (C++11 起)
这就是带有谓词的
condition_variable
重载可以让您避免虚假唤醒。不需要用于检查虚假唤醒的手工 while 循环。
For the operation after wake up
According to wake's doc
Means your code is atomic between
#1
to#2
when it's woken up. Don't need to worry about the synchronization since your compiler should handle it.For spurious wakeup
The second overload with predicate would handle spurious wakeup for you.
template< class Predicate > void wait( std::unique_lock<std::mutex>& lock, Predicate stop_waiting ); (2) (since C++11)
That is the
condition_variable
overload with predicate would save you from spurious wake up.The handmade while loop for checking spurious wake up is not needed.