代码的问题
// Ensures the mutex will be locked
while(myWaitMutex.try_lock());
.try_lock()
尝试获取锁并true
在成功时返回,即代码说“如果我们获取锁,则一次又一次地重试锁定它,直到我们失败”。我们永远不会“失败”,因为我们目前拥有自己正在等待的锁,因此这将是一个无限循环。此外,尝试使用std::mutex
调用者已经获得锁定的 UB 进行锁定,因此保证是 UB。如果不成功,.try_lock()
将返回false
并while
退出循环。换句话说,这并不能确保互斥锁会被锁定。
确保互斥锁被锁定的正确方法很简单:
myWaitMutex.lock();
这将导致当前线程(无限期地)阻塞,直到它可以获取锁。
接下来,另一个线程尝试解锁它没有锁定的互斥锁。
// Executed when thread 1 should resume processing:
myWaitMutex.unlock();
这将不起作用,因为它是您尚未锁定的.unlock()
UB std::mutex
。
使用锁
使用互斥锁时,更容易使用 RAII 所有权包装对象,例如std::lock_guard
. 的使用模式std::mutex
始终是:“锁定 -> 在临界区做某事 -> 解锁”。Astd::lock_guard
将在其构造函数中锁定互斥锁,并在其析构函数中解锁。无需担心何时锁定和解锁以及诸如此类的低级内容。
std::mutex m;
{
std::lock_guard<std::mutex> lk{m};
/* We have the lock until we exit scope. */
} // Here 'lk' is destroyed and will release lock.
一个简单的锁可能不是完成这项工作的最佳工具
如果您想要的是能够发出唤醒线程的信号,那么等待和通知结构使用std::condition_variable
. 允许任何调用者在不持有任何锁std::condition_variable
的情况下向等待线程发送信号。
#include <atomic>
#include <condition_variable>
#include <iostream>
#include <mutex>
#include <thread>
using namespace std::literals;
int main() {
std::mutex m;
std::condition_variable cond;
std::thread t{[&] {
std::cout << "Entering sleep..." << std::endl;
std::unique_lock<std::mutex> lk{m};
cond.wait(lk); // Will block until 'cond' is notified.
std::cout << "Thread is awake!" << std::endl;
}};
std::this_thread::sleep_for(3s);
cond.notify_all(); // Notify all waiting threads.
t.join(); // Remember to join thread before exit.
}
然而,更复杂的是,有一个叫做虚假唤醒的东西,这意味着任何等待的线程都可能在任何时候因为未知的原因唤醒。这在大多数系统上都是事实,并且与线程调度的内部工作有关。此外,我们可能需要检查在处理并发时是否真的需要等待。例如,如果通知线程在我们开始等待之前发生了通知,那么我们可能会永远等待,除非我们有办法首先检查这一点。
为了处理这个问题,我们需要添加一个 while 循环和一个谓词,告诉我们何时需要等待以及何时完成等待。
int main() {
std::mutex m;
std::condition_variable cond;
bool done = false; // Flag for indicating when done waiting.
std::thread t{[&] {
std::cout << "Entering sleep..." << std::endl;
std::unique_lock<std::mutex> lk{m};
while (!done) { // Wait inside loop to handle spurious wakeups etc.
cond.wait(lk);
}
std::cout << "Thread is awake!" << std::endl;
}};
std::this_thread::sleep_for(3s);
{ // Aquire lock to avoid data race on 'done'.
std::lock_guard<std::mutex> lk{m};
done = true; // Set 'done' to true before notifying.
}
cond.notify_all();
t.join();
}
正如@David Schwartz的评论中提到的那样,在循环中等待并使用诸如“被盗唤醒”之类的谓词是一个好主意,还有其他原因。