当前位置: 首页 > 知识库问答 >
问题:

在多线程中使用std::mutex保护cout的死锁

申屠泳
2023-03-14

在多个线程中使用cout可能会导致交叉输出<所以我试着用互斥来保护cout。

以下代码使用std::async启动10个后台线程。当线程启动时,它会打印“已启动线程…”。主线程按照创建后台线程的顺序迭代这些线程的未来,并打印出“donethread…”当相应的线程完成时。

输出是正确同步的,但是在一些线程已经启动,一些线程已经完成(请参见下面的输出)之后,会发生死锁。所有后台线程都已离开,主线程正在等待互斥锁。

僵局的原因是什么?

当print函数离开或者for循环的一次迭代结束时,lock_-guard应该解锁互斥锁,以便等待的线程之一能够继续。

为什么所有的线都饿了?

密码

#include <future>
#include <iostream>
#include <vector>

using namespace std;
std::mutex mtx;           // mutex for critical section

int print_start(int i) {
   lock_guard<mutex> g(mtx);
   cout << "Started thread" << i << "(" << this_thread::get_id() << ") " << endl;
   return i;
}

int main() {
   vector<future<int>> futures;

   for (int i = 0; i < 10; ++i) {
      futures.push_back(async(print_start, i));
   }

   //retrieve and print the value stored in the future
   for (auto &f : futures) {
      lock_guard<mutex> g(mtx);
      cout << "Done thread" << f.get() << "(" << this_thread::get_id() << ")" << endl;
   }
   cin.get();
   return 0;
}

输出

Started thread0(352)
Started thread1(14944)
Started thread2(6404)
Started thread3(16884)
Done thread0(16024)
Done thread1(16024)
Done thread2(16024)
Done thread3(16024)

共有3个答案

孔星宇
2023-03-14

好在从源头上发现了原因。然而,经常发生的错误可能不那么容易定位。原因也可能不同。幸运的是,在死锁的情况下,您可以使用调试器来调查它。

我编译并运行了您的示例,然后在使用gdb(gcc 4.9.2/Linux)附加到它之后,有一个回溯(跳过了嘈杂的实现细节):

#0  __lll_lock_wait ()
...
#5  0x0000000000403140 in std::lock_guard<std::mutex>::lock_guard (
    this=0x7ffe74903320, __m=...) at /usr/include/c++++/4.9/mutex:377
#6  0x0000000000402147 in print_start (i=0) at so_deadlock.cc:9
...
#23 0x0000000000409e69 in ....::_M_complete_async() (this=0xdd4020)
    at /usr/include/c++/4.9/future:1498
#24 0x0000000000402af2 in std::__future_base::_State_baseV2::wait (
    this=0xdd4020) at /usr/include/c++/4.9/future:321
#25 0x0000000000404713 in std::__basic_future<int>::_M_get_result (
    this=0xdd47e0) at /usr/include/c++/4.9/future:621
#26 0x0000000000403c48 in std::future<int>::get (this=0xdd47e0)
    at /usr/include/c++/4.9/future:700
#27 0x000000000040229b in main () at so_deadlock.cc:24

这正是其他答案中所解释的——locked部分(so_deadlock.cc:24)中的代码调用future::get(),而future::get()反过来(通过强制结果)尝试再次获取锁。

在其他情况下可能没那么简单,通常有几个线程,但都在那里。

澹台景辉
2023-03-14

锁定互斥体,然后等待其中一个未来,这反过来需要对互斥体本身进行锁定。简单规则:不要使用锁定的互斥锁等待。

顺便说一句:锁定输出流不是很有效,因为它很容易被您甚至无法控制的代码绕过。不要使用这些全局变量,而是为需要输出某些内容(依赖项注入)的代码提供一个流,然后以线程安全的方式从该流中收集数据。或者使用日志库,因为这可能正是您想要做的。

吴腾
2023-03-14

你的问题在于使用Future::get

当共享值处于“已存储”状态时,将返回(或抛出)共享值。

如果共享状态尚未就绪(即,提供程序尚未设置其值或异常),则函数将阻塞调用线程并等待它就绪。

http://www.cplusplus.com/reference/future/future/get/

因此,如果未来的线程还没有开始运行,那么函数将一直阻塞,直到该线程完成。但是,在调用future::get之前,您拥有互斥锁的所有权,因此,您等待的任何线程都无法自己获得互斥锁。

这将解决死锁问题:

int value = f.get();
lock_guard<mutex> g(mtx);
cout << "Done thread" << value << "(" << this_thread::get_id() << ")" << endl;
 类似资料:
  • 我在理解条件变量及其在互斥体中的使用时遇到了一些困难,我希望社区能帮助我。请注意,我来自win32背景,因此与CRITICAL_SECTION、HANDLE、SetEvent、WaitForMultipleObject等一起使用。 这是我第一次尝试使用C++11标准库进行并发操作,它是在这里找到的一个程序示例的修改版本。 关于这个的几个问题。 我读过“任何要等待std::condition_var

  • 我知道互斥体通常也会保护共享数据,使用原子变量只是一个例子。问题不是如何保护共享数据,而是是否需要使用相同的互斥体来保护两者。另一个使用第二互斥体的示例:

  • std::cout是std::ostream的一个实例。我可以在一个名为usr/include/c/7/iostream的文件中看到std::cout的声明: 而std::ostream由typedef std::basic\u ostream定义 此外,您似乎无法创建std::ostream的实例。请参阅此演示代码片段: 以下是编译器对上述代码段的抱怨: 问题来了,因为

  • 本文向大家介绍【java 多线程】守护线程与非守护线程的详解,包括了【java 多线程】守护线程与非守护线程的详解的使用技巧和注意事项,需要的朋友参考一下 Java中有两类线程:User Thread(用户线程)、Daemon Thread(守护线程) 用户线程即运行在前台的线程,而守护线程是运行在后台的线程。 守护线程作用是为其他前台线程的运行提供便利服务,而且仅在普通、非守护线程仍然运行时才需

  • 我使用test_and_set方法让其他线程先等待,线程t1可以中断while循环,但即使在线程t1将值设置为0之后。线程t2继续运行while循环,它不会中断while循环。应该做哪些改变?。

  • 我有一个类似的问题,但是我知道当我要求阅读一行时,发件人应该发送一个行尾。 让我困惑的是,在调试中,它是有效的。可能是因为我在调试时跳过的顺序(直到现在我都不知道这会有什么不同),但我想更好地理解它。 我已经使用线程,但不是很多。 这是我的服务器类: 线程(基于此) 和客户: 它似乎在某个地方进入了死锁,出于某种原因,除非在调试中运行,否则永远不要在向客户端发送数据的服务器类上输入该死锁 (顺便说