使用具有相同条件变量的不同互斥体有意义吗?
Does it make sense to use different mutexes with the same condition variable?
cppreference.com 条件变量的 notify_one() 函数的文档说明如下
The notifying thread does not need to hold the lock on the same mutex as the one held by the waiting thread(s); in fact doing so is a pessimization, since the notified thread would immediately block again, waiting for the notifying thread to release the lock.
这句话的第一部分很奇怪,如果我在 notifying 和 notified 线程中持有不同的互斥锁,那么互斥锁有没有实际意义,因为这里没有 'blocking' 操作。事实上,如果持有不同的互斥锁,那么虚假唤醒可能导致通知丢失的可能性是有可能的!我的印象是在这种情况下我们最好不要锁定 notifying 线程。有人可以澄清一下吗?
以 cppreference 页面中关于条件变量的以下内容为例。
std::mutex m; // this is supposed to be a pessimization
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// Wait until main() sends data
std::unique_lock<std::mutex> lk(m); // a different, local std::mutex is supposedly better
cv.wait(lk, []{return ready;});
// after the wait, we own the lock.
std::cout << "Worker thread is processing data\n";
data += " after processing";
// Send data back to main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";
// send data to the worker thread
{
std::lock_guard<std::mutex> lk(m); // a different, local std::mutex is supposedly better
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return processed;});
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
PS。我看到几个标题相似的问题,但它们指的是问题的不同方面。
The notifying thread does not need to hold the lock on the same mutex as the one held by the waiting thread(s);
这是误导。问题在于“相同”这个词。他们应该说,“...不需要在 any 互斥量上保持锁定...”这才是真正的重点。等待线程在进入 wait()
调用时应该锁定一个互斥锁的一个重要原因是:它正在等待某个共享数据结构中的某些更改,并且在访问该结构时需要锁定互斥锁以检查是否等待的变化是否真的发生了。
notify()ing
线程可能需要锁定相同的互斥体才能实现该更改,但程序的正确性不取决于它是在释放之前还是之后调用 notify()
互斥锁。
我觉得这里cppreference的写法有点别扭。我认为他们只是想将与条件变量一起使用的互斥锁与其他不相关的互斥锁区分开来。
使用具有不同互斥量的条件变量是没有意义的。互斥锁用于对实际语义条件(在示例中它只是变量 ready
)进行任何原子更改,因此必须在更新或检查条件时保持它。还需要确保未阻塞的等待线程可以立即检查条件而不会再次 运行 进入竞争条件。
我的理解是这样的:
可以,在调用 notify_one
时不持有与条件变量相关联的互斥量的锁,或者根本不持有任何互斥量,但是出于不同的原因持有其他互斥量是可以的。
悲观的不是只使用一个互斥锁,而是当你知道另一个线程应该在收到通知后立即尝试获取互斥锁时,持有这个互斥锁的时间超过必要的时间。
我认为我的解释与cppreference on condition variable中给出的解释一致:
The thread that intends to modify the shared variable has to
acquire a std::mutex
(typically via std::lock_guard)
perform the modification while the lock is held
execute notify_one
or notify_all
on the std::condition_variable
(the lock does not need to be held for notification)
Even if the shared variable is atomic, it must be modified under the mutex in order to correctly publish the modification to the waiting thread.
Any thread that intends to wait on std::condition_variable
has to
acquire a std::unique_lock<std::mutex>
, on the same mutex as used to protect the shared variable
此外 standard 明确禁止对 wait
、wait_for
或 wait_until
:
使用不同的互斥量
lock.mutex() returns the same value for each of the lock arguments supplied by all concurrently waiting (via wait, wait_for, or wait_until) threads.
cppreference.com 条件变量的 notify_one() 函数的文档说明如下
The notifying thread does not need to hold the lock on the same mutex as the one held by the waiting thread(s); in fact doing so is a pessimization, since the notified thread would immediately block again, waiting for the notifying thread to release the lock.
这句话的第一部分很奇怪,如果我在 notifying 和 notified 线程中持有不同的互斥锁,那么互斥锁有没有实际意义,因为这里没有 'blocking' 操作。事实上,如果持有不同的互斥锁,那么虚假唤醒可能导致通知丢失的可能性是有可能的!我的印象是在这种情况下我们最好不要锁定 notifying 线程。有人可以澄清一下吗?
以 cppreference 页面中关于条件变量的以下内容为例。
std::mutex m; // this is supposed to be a pessimization
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
void worker_thread()
{
// Wait until main() sends data
std::unique_lock<std::mutex> lk(m); // a different, local std::mutex is supposedly better
cv.wait(lk, []{return ready;});
// after the wait, we own the lock.
std::cout << "Worker thread is processing data\n";
data += " after processing";
// Send data back to main()
processed = true;
std::cout << "Worker thread signals data processing completed\n";
lk.unlock();
cv.notify_one();
}
int main()
{
std::thread worker(worker_thread);
data = "Example data";
// send data to the worker thread
{
std::lock_guard<std::mutex> lk(m); // a different, local std::mutex is supposedly better
ready = true;
std::cout << "main() signals data ready for processing\n";
}
cv.notify_one();
// wait for the worker
{
std::unique_lock<std::mutex> lk(m);
cv.wait(lk, []{return processed;});
}
std::cout << "Back in main(), data = " << data << '\n';
worker.join();
}
PS。我看到几个标题相似的问题,但它们指的是问题的不同方面。
The notifying thread does not need to hold the lock on the same mutex as the one held by the waiting thread(s);
这是误导。问题在于“相同”这个词。他们应该说,“...不需要在 any 互斥量上保持锁定...”这才是真正的重点。等待线程在进入 wait()
调用时应该锁定一个互斥锁的一个重要原因是:它正在等待某个共享数据结构中的某些更改,并且在访问该结构时需要锁定互斥锁以检查是否等待的变化是否真的发生了。
notify()ing
线程可能需要锁定相同的互斥体才能实现该更改,但程序的正确性不取决于它是在释放之前还是之后调用 notify()
互斥锁。
我觉得这里cppreference的写法有点别扭。我认为他们只是想将与条件变量一起使用的互斥锁与其他不相关的互斥锁区分开来。
使用具有不同互斥量的条件变量是没有意义的。互斥锁用于对实际语义条件(在示例中它只是变量 ready
)进行任何原子更改,因此必须在更新或检查条件时保持它。还需要确保未阻塞的等待线程可以立即检查条件而不会再次 运行 进入竞争条件。
我的理解是这样的:
可以,在调用 notify_one
时不持有与条件变量相关联的互斥量的锁,或者根本不持有任何互斥量,但是出于不同的原因持有其他互斥量是可以的。
悲观的不是只使用一个互斥锁,而是当你知道另一个线程应该在收到通知后立即尝试获取互斥锁时,持有这个互斥锁的时间超过必要的时间。
我认为我的解释与cppreference on condition variable中给出的解释一致:
The thread that intends to modify the shared variable has to
acquire a
std::mutex
(typically via std::lock_guard)perform the modification while the lock is held
execute
notify_one
ornotify_all
on thestd::condition_variable
(the lock does not need to be held for notification)
Even if the shared variable is atomic, it must be modified under the mutex in order to correctly publish the modification to the waiting thread.
Any thread that intends to wait on
std::condition_variable
has to acquire astd::unique_lock<std::mutex>
, on the same mutex as used to protect the shared variable
此外 standard 明确禁止对 wait
、wait_for
或 wait_until
:
lock.mutex() returns the same value for each of the lock arguments supplied by all concurrently waiting (via wait, wait_for, or wait_until) threads.