为什么printf会导致与future.get的死锁,而cout则不会?



我对 MPI 和 CUDA 有一些经验,现在我决定是时候实际做一些线程了。我正在学习C++标准库线程的东西,并且(基于一系列 Youtube 视频(我正在构建一段简单的代码,它使用 std::p ackaged_task 构建作业并将其发送到作业队列以供工作线程执行。到目前为止足够简单。

当我尝试通过未来检索作业结果时,问题就开始了:

printf_mutex.lock();
printf("MAIN: Result of %i! is %in", 6, future_result_of_packaged_task.get()); // this causes deadlock!
printf_mutex.unlock();

这将永远锁定代码! 但这有效:

int mah_result = future_result_of_packaged_task.get();
printf_mutex.lock();
printf("MAIN: Result of %i! is %in", 6, mah_result ); // this is okay
printf_mutex.unlock();

就像这个(这就是YouTuber所做的(:

std::cout << future_result_of_packaged_task.get() << "n"; //this is okay

为什么 PRINTF(( 在 COUT 正常工作时失败?

我认为理解这个问题可能很有教育意义。

整个代码非常简单(不需要一些库,因为我只是懒惰地从以前的玩具代码中复制粘贴它们,但谁在乎(:

#include <cstdio>
#include <thread>
#include <string>
#include <mutex>
#include <condition_variable>
#include <iostream>
#include <future>
#include <deque>
int factorial(int N, std::mutex& printf_mutex)
{
int result = 1;
for (int i = N; i > 1; --i) result *= i;
printf_mutex.lock();
printf("FACTORIAL: Result of %i! is %in", N, result);
printf_mutex.unlock();
return result;
}
void worker_thread( std::deque< std::packaged_task<int()> >& task_queue, std::mutex& task_queue_mutex, std::condition_variable& task_queue_cv, std::mutex& printf_mutex )
{
std::unique_lock<std::mutex> task_queue_mutex_lock(task_queue_mutex);
task_queue_cv.wait(task_queue_mutex_lock, [&](){return !task_queue.empty();} );
printf_mutex.lock();
printf("WORKER: I'm not sleeping anymore!n"); // this is okay
printf_mutex.unlock();
std::packaged_task<int()> my_task = std::move( task_queue.front() );
task_queue.pop_front();
my_task();
}
int main()
{
std::mutex printf_mutex;
std::mutex task_queue_mutex;
std::deque< std::packaged_task<int()> > task_queue;
std::condition_variable task_queue_cv;
std::thread a_thread( worker_thread, std::ref(task_queue), std::ref(task_queue_mutex), std::ref(task_queue_cv), std::ref(printf_mutex) );
std::this_thread::sleep_for(std::chrono::seconds(1));
std::packaged_task<int()> a_task( bind(factorial, 6, std::ref(printf_mutex)) );
std::future<int> future_result_of_packaged_task = a_task.get_future();
task_queue_mutex.lock();
task_queue.push_back(std::move(a_task));
task_queue_mutex.unlock();
task_queue_cv.notify_one();
printf_mutex.lock();
printf("MAIN: Notification sent!n"); // this is okay
printf_mutex.unlock();
//std::cout << future_result_of_packaged_task.get() << "n"; //this is okay
int mah_result = future_result_of_packaged_task.get();
printf_mutex.lock();
printf("MAIN: Result of %i! is %in", 6, mah_result ); // this is okay
printf_mutex.unlock();
printf_mutex.lock();
//printf("MAIN: Result of %i! is %in", 6, future_result_of_packaged_task.get()); // this causes a deadlock!
printf_mutex.unlock();
a_thread.join();
return 0;
}

是的,我讨厌C++iostream,是的,我讨厌std::locks,它们的存在本身就冒犯了奥卡姆剃刀。我还为我的玩具代码使用了可怕的命名方案。对于这个问题来说,这些都不重要。

编辑:因此,从公认的答案来看,难题的解决方案并不明显。我想说清楚: 1.用printf_mutex保护木管会使它失效以及打印f。这表明问题要么是 future.get(( 干扰了我机器上的输出机制,要么是互斥冲突/竞赛。当有疑问时,总是怀疑一个种族,并注意:
2. future.get(( 是一个阻塞函数。我有效地锁定了一个互斥锁并睡觉,这是要求比赛。那场比赛在哪里可以发生艰难?通过实验,我们知道它永远不会发生在工作线程中。它还可能发生在哪里?
3. 答案是阶乘也尝试锁定printf_mutex并失败,因为主总是先锁定它,然后在 future.get(( 中进入睡眠状态。

接受的答案是提供最强/最完整的线索的答案。

你拿着printf_mutex,所以任务无法完成,future_result_of_packaged_task.get()永远不会返回。您的其他示例在调用get时不持有互斥锁,因此不要死锁。

您在锁和最低printf_mutex.lock()之间worker_thread竞争条件(您可以通过在其printf_mutex.lock();之前放置一个std::this_thread::sleep_for(std::chrono::seconds(1));来查看它(,而且,如果幸运地worker_thread获胜,那么您在锁定内部factorial和最低printf_mutex.lock()之间遇到了互斥逻辑问题。factorial将被永久锁定,因为它是在printf_mutex被锁定后调用的。要么锁定在factorial内部,要么在外部printf之前锁定.

std::coutprintf一起工作与否只是运气。std::cout可能比printf慢或快,具体取决于您的优化指令。请注意,您之前使用std::cout没有printf_mutex.lock();,这就是它工作的原因。此外,在printf有效的情况下,您在锁定之前调用.get(),这就是它工作的原因。修复竞争条件和锁定逻辑,然后printfstd::cout都可以工作。


Obs.:更喜欢使用RAII锁定模式,也可以考虑对相同的螺纹锁使用std::recursive_mutex,对于打印,这可能非常有帮助。

最新更新