我对std::condition_变量的用法有点困惑。我知道在调用条件变量之前,我必须在
。我找不到的是,在调用互斥锁
上创建一个唯一锁
。等待()notify_one()
或notify_all()
之前,我是否还应该获取一个唯一的锁。
cppreference.com的例子是相互矛盾的。例如,notify_one页面给出了这个例子:
#include <iostream>
#include <condition_variable>
#include <thread>
#include <chrono>
std::condition_variable cv;
std::mutex cv_m;
int i = 0;
bool done = false;
void waits()
{
std::unique_lock<std::mutex> lk(cv_m);
std::cout << "Waiting... \n";
cv.wait(lk, []{return i == 1;});
std::cout << "...finished waiting. i == 1\n";
done = true;
}
void signals()
{
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "Notifying...\n";
cv.notify_one();
std::unique_lock<std::mutex> lk(cv_m);
i = 1;
while (!done) {
lk.unlock();
std::this_thread::sleep_for(std::chrono::seconds(1));
lk.lock();
std::cerr << "Notifying again...\n";
cv.notify_one();
}
}
int main()
{
std::thread t1(waits), t2(signals);
t1.join(); t2.join();
}
在这里,锁不是为第一个notify_one()
获取的,而是为第二个notify_one()
获取的。查看其他页面上的示例,我看到了不同的东西,大多数情况下没有获得锁。
notify_one()
之前,我可以选择自己锁定互斥锁吗?我为什么选择锁定它
匿名用户
使用vc10和Boost 1.56,我实现了一个并发队列,就像这篇博客文章所建议的那样。作者解锁互斥锁以最小化争用,即在解锁互斥锁的情况下调用notify_one()
:
void push(const T& item)
{
std::unique_lock<std::mutex> mlock(mutex_);
queue_.push(item);
mlock.unlock(); // unlock before notificiation to minimize mutex contention
cond_.notify_one(); // notify one waiting thread
}
解锁互斥锁有Boost文档中的一个示例支持:
void prepare_data_for_processing()
{
retrieve_data();
prepare_data();
{
boost::lock_guard<boost::mutex> lock(mut);
data_ready=true;
}
cond.notify_one();
}
然而,这导致了以下不稳定的行为:
- 而
notify_one()
尚未调用。wait()仍然可以通过boost::thread::interrupt()
- 一旦第一次调用了
notify_one()
。wait()死锁;等待不能再通过boost::thread::interrupt()
或boost::condition_variable::notify_*()
来结束
移除行mlock。解锁()
使代码按预期工作(通知和中断结束等待)。请注意,notify_one()
void push(const T& item)
{
std::lock_guard<std::mutex> mlock(mutex_);
queue_.push(item);
cond_.notify_one(); // notify one waiting thread
}
这意味着,至少在我的特定线程实现中,在调用boost::condition_variable::notify_one()
之前,不能解锁互斥锁,尽管这两种方法似乎都是正确的。
正如其他人所指出的,在调用notify_one()
时,您不需要在种族条件和线程相关问题方面持有锁。但是,在某些情况下,在调用notify_one()
之前,可能需要保持锁以防止condition_变量
被破坏。考虑以下示例:
thread t;
void foo() {
std::mutex m;
std::condition_variable cv;
bool done = false;
t = std::thread([&]() {
{
std::lock_guard<std::mutex> l(m); // (1)
done = true; // (2)
} // (3)
cv.notify_one(); // (4)
}); // (5)
std::unique_lock<std::mutex> lock(m); // (6)
cv.wait(lock, [&done]() { return done; }); // (7)
}
void main() {
foo(); // (8)
t.join(); // (9)
}
假设在我们创建新创建的线程t
之后但在我们开始等待条件变量之前(介于(5)和(6)之间)有一个上下文切换。线程t
获取锁(1),设置谓词变量(2),然后释放锁(3)。假设在执行notify_one()
(4)之前此时还有另一个上下文切换。主线程获取锁(6)并执行第(7)行,此时谓词返回true
并且没有等待的理由,因此它释放锁并继续。foo
返回(8)并且其范围内的变量(包括cv
)被销毁。在线程t
可以加入主线程(9)之前,它必须完成它的执行,因此它从中断的地方继续执行cv.notify_one()
(4),此时cv
已经被销毁!
在这种情况下,可能的修复方法是在调用notify_one
时保持锁定(即移除以第(3)行结尾的作用域)。通过这样做,我们确保线程t
在cv之前调用
可以检查新设置的谓词变量并继续,因为它需要获取锁,而锁是notify_one
。waitt
总而言之,这个特定案例中的问题实际上与线程无关,而是与引用捕获的变量的生命周期有关cv
通过线程t
通过引用捕获,因此必须确保cv
在线程执行期间保持活动状态。这里给出的其他示例没有遇到这个问题,因为条件_变量
和互斥对象是在全局范围内定义的,因此它们保证在程序退出之前保持活动状态。
在调用条件变量::notify_one()
时,不需要持有锁,但它仍然是定义良好的行为,而不是错误,这并不是错误。
然而,这可能是一种“悲观情绪”,因为任何使等待线程可运行的线程(如果有的话)都会立即尝试获取通知线程持有的锁。我认为在调用notify_one()
或notify_all()
时,避免持有与条件变量相关联的锁是一个很好的经验法则。请参阅Pthread Mutex:Pthread_Mutex_unlock()消耗大量时间,例如,在调用Pthread等价于notify_one()
的函数之前释放锁,可以显著提高性能。
请记住,在某个时候,while
循环中的lock()
调用是必要的,因为在while(!完成)
循环条件检查期间需要保持锁。但是调用notify_one()
不需要保持它。
2016-02-27:针对评论中关于是否存在竞态条件的一些问题,进行了大量更新,即锁对notify_one()
调用没有帮助。我知道这个更新很晚,因为这个问题是两年前提出的,但我想解决@Cookie关于可能的竞争条件的问题,如果生产者(signals()
在本例中)在消费者(wait()
在本例中)能够调用wait()
之前调用notify_one()
。
关键是i
发生了什么-这是实际指示消费者是否有“工作”要做的对象。condition_变量
只是一种机制,让使用者高效地等待对i
的更改。
生产者需要在更新i
时持有锁,消费者必须在检查i
并调用条件变量::wait()
时持有锁(如果它需要等待)。在这种情况下,关键是当消费者进行此检查并等待时,它必须是持有锁(通常称为关键部分)的同一个实例。由于关键部分在生产者更新i
和消费者检查并等待i
时保持,因此i
没有机会在消费者检查i
和调用条件变量::wait()
之间切换。这是正确使用条件变量的关键。
C标准表示,使用谓词调用condition_variable::wait()时,其行为如下(如本例所示):
while (!pred())
wait(lock);
当消费者检查i
时,可能会出现两种情况:
>
如果消费者呼叫cv时
,实现的i
已为1。wait()等待(锁定)
部分将永远不会被调用,因为while(!pred())
测试将导致内部循环终止。在这种情况下,无论何时调用notify_one(),消费者都不会阻止。
这里的示例确实具有额外的复杂性,即使用done
变量向生产者线程发回信号,表明消费者已经识别出i==1
,但我认为这根本不会改变分析,因为所有对done
(用于读取和修改)的访问都是在涉及I
和条件变量
的相同关键部分中完成的。
如果你看一下@eh9指出的问题,使用std::atomic和std::condition_变量同步是不可靠的,你会看到一个竞争条件。然而,该问题中发布的代码违反了使用条件变量的基本规则之一:在执行检查和等待时,它不包含任何关键部分。
在该示例中,代码如下所示:
if (--f->counter == 0) // (1)
// we have zeroed this fence's counter, wake up everyone that waits
f->resume.notify_all(); // (2)
else
{
unique_lock<mutex> lock(f->resume_mutex);
f->resume.wait(lock); // (3)
}
您会注意到#3处的etc()
是在持有f-时执行的
在这个问题的例子中,这种种族并不存在。
问题内容: 我在服务器端有PHP,在客户端有HTML和javascript。 我正在制作一个应用程序,其中利益相关者键入一条消息,该消息实时广播到一个组的多个接收者。 我在Google上做了一些研究,我了解我需要使用WebSockets或Comet进行实时推送通知。WebSocket或Comet是向用户发送大量通知的必需项吗? 我的理解正确吗?有什么参考开始吗? 问题答案: 如果客户端是浏览器,则
问题内容: 我收到此错误。如下面的代码所示,该;行位于代码行之前;。在我的每一项活动中,此onCreate()代码都是相同的格式,到目前为止,我从没有遇到过麻烦。自从我将ADT更新为22以来,到处都出现了许多随机错误。我已经克服了许多错误,这是我的最新错误。 我该如何解决此错误? 问题答案: 我也遇到了这个问题,但是当我在调用super.onCreate()之前调用窗口请求时,问题就解决了,请尝试
我有以下代码: 假设线程1调用synchornizer.await()并通过 和块在 然后,另一个线程2调用synchronizer.signalAll()以向线程1发送信号。我的问题是线程2如何通过调用 打电话之前 锁最初是由线程 1 获取的? 我在这里发现了同样的问题: 等待可重入锁中的条件 答案是: “锁定”和“已同步”都暂时允许其他人在等待时获取锁定。要停止等待,线程必须重新获取锁。 我试
Python有一个名为<code>Condition或<code>notify_all()。然而,在调用<code>wait()acquire()wait()方法然后释放锁并等待通知,然后它将继续重新获取锁,您可以运行一些需要线程安全的代码。我的问题是,当调用<code>wait()方法时,<code>条件</code>对象为什么不在内部自动获取锁: 必须调用其他方法并按住关联的锁。 方法释放锁,
从理论上讲,一个等待线程(假设Thread_1)首先获取一个互斥体,然后通过调用wait()等待条件变量。对wait()的调用会立即解锁互斥体。当另一个线程(比如Thread_2)调用notify()时,等待的线程(Thread_1)被唤醒,相同的互斥体在等待之前被锁定(..)调用返回。 现在假设多个线程在给定的时间等待一个条件变量(假设是Thread_1、Thread_2和Thread_3)。现
问题内容: 为什么不起作用? 无法理解为什么作业的左侧不是变量。 有人帮忙吗? 问题答案: 如果您只想增加5,而又不限于此,则可以避免冗长,而可以这样做: 这将in- in 的值原地增加5。