我对MPI和CUDA有一定的经验,现在我决定是时候实际进行一些线程化了。我正在学习C ++标准库线程问题,并且(基于一系列Youtube视频)正在构建一个简单的代码,该代码使用std :: packaged_task构建作业并将其发送到作业队列以供工作线程执行。到目前为止很简单。
当我尝试通过将来检索作业结果时,问题开始了:
printf_mutex.lock();
printf("MAIN: Result of %i! is %i\n", 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 %i\n", 6, mah_result ); // this is okay
printf_mutex.unlock();
这样做(这是youtuber所做的):
std::cout << future_result_of_packaged_task.get() << "\n"; //this is okay
为什么COUT正确工作时PRINTF()为什么会失败?
我认为了解这个问题可能非常有教育意义。
整个代码非常简单(不需要某些库,因为我只是从以前的玩具代码中懒洋洋地复制了它们,但谁在乎呢?):
#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 %i\n", 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 %i\n", 6, mah_result ); // this is okay
printf_mutex.unlock();
printf_mutex.lock();
//printf("MAIN: Result of %i! is %i\n", 6, future_result_of_packaged_task.get()); // this causes a deadlock!
printf_mutex.unlock();
a_thread.join();
return 0;
}
是的,我讨厌C ++ iostream,是的,我讨厌std :: locks,它们的存在侵犯了Occam的Razor。我还对玩具代码使用了可怕的命名方案。对于棘手的问题,这都不重要。
编辑:因此,从已接受的答案中,难题的解决方案并不明显。我想说清楚:
1.使用printf_mutex保护cout会使cout和printf一样失败。这表明问题可能是future.get()干扰了我机器上的输出机制,或者是互斥体冲突/竞赛。如有疑问,请始终怀疑种族,并注意:
2. future.get()是一个阻止函数。我有效地锁定了一个互斥锁并入睡,这是一场比赛。那场比赛在哪里艰难进行?通过实验,我们知道它永远不会在工作线程中发生。还有什么可能发生?
3。答案是阶乘也尝试锁定printf_mutex并失败,因为主线程始终先锁定它,然后在将来进入休眠状态。get()
可接受的答案是提供最强/最完整线索的答案。
答案 0 :(得分:3)
您持有printf_mutex
,因此任务无法完成,future_result_of_packaged_task.get()
再也不会返回。您的其他示例在调用get
时不保留互斥体,因此请不要死锁。
答案 1 :(得分:0)
您的竞争条件介于worker_thread
和最低的printf_mutex.lock()
之间(您可以通过在std::this_thread::sleep_for(std::chrono::seconds(1));
之前放置printf_mutex.lock();
来查看它) ,很幸运,worker_thread
获胜,您在factorial
内部锁定和最低printf_mutex.lock()
内部锁定之间遇到了互斥逻辑问题。 factorial
将永远被锁定,因为它在printf_mutex
被锁定之后被调用。您可以锁定在factorial
内部,也可以锁定在外部printf
之前。
无论是否使用std::cout
或printf
都是运气。 std::cout
可以比printf
慢或快,具体取决于您的优化指令。请注意,您之前使用的是std::cout
,而之前没有printf_mutex.lock();
,这就是为什么它起作用的原因。另外,在有效的printf
情况下,您在锁定之前调用了.get()
,这就是它起作用的原因。修复争用条件和锁定逻辑,然后printf
和std::cout
都可以工作。
不好。:最好使用RAII锁定模式,也可以考虑对同一线程锁定使用std::recursive_mutex
,以便进行打印,