使用C ++ 11 STL和VS2013实现异步打印类。 无法获取thread.join()返回没有死锁。 我正在尝试调试,最后发现这个问题可能是由全局/本地类变量声明引起的。这是细节,我不知道为什么会这样?
#include <iostream>
#include <string>
#include <chrono>
#include <mutex>
#include <thread>
#include <condition_variable>
#include "tbb/concurrent_queue.h"
using namespace std;
class logger
{
public:
~logger()
{
fin();
}
void init()
{
m_quit = false;
m_thd = thread(bind(&logger::printer, this));
//thread printer(bind(&logger::printer, this));
//m_thd.swap(printer);
}
void fin()
{
//not needed
//unique_lock<mutex> locker(m_mtx);
if (m_thd.joinable())
{
m_quit = true;
write("fin");
//locker.unlock();
m_thd.join();
}
}
void write(const char *msg)
{
m_queue.push(msg);
m_cond.notify_one();
}
void printer()
{
string msgstr;
unique_lock<mutex> locker(m_mtx);
while (1)
{
if (m_queue.try_pop(msgstr))
cout << msgstr << endl;
else if (m_quit)
break;
else
m_cond.wait(locker);
}
cout << "printer quit" <<endl;
}
bool m_quit;
mutex m_mtx;
condition_variable m_cond;
thread m_thd;
tbb::concurrent_queue<string> m_queue;
};
为了更方便,我将thread.join放入了类的析构函数中,以确保m_thread可以正常退出。 我测试了全班并发生了错误。 m_thd.join()在类logger声明为全局变量时永远不会返回 像这样:
logger lgg;
void main()
{
lgg.init();
for (int i = 0; i < 100; ++i)
{
char s[8];
sprintf_s(s, 8, "%d", i);
lgg.write(s);
}
//if first call lgg.fin() here, m_thd can be joined normally
//lgg.fin();
system("pause");
//dead&blocked here and I observed that printer() finished successfully
}
如果将类记录器声明为局部变量,似乎一切正常。
void main()
{
logger lgg;
lgg.init();
for (int i = 0; i < 100; ++i)
{
char s[8];
sprintf_s(s, 8, "%d", i);
lgg.write(s);
}
system("pause");
}
更新2015/02/27
fin()
答案 0 :(得分:0)
我想知道你是如何使用m_mtx
的。正常模式是两个线程锁定它并且两个线程都解锁它。但是fin()
无法锁定它。
同样意外的是m_cond.wait(m_mtx)
。这会释放互斥锁,除了它首先没有被锁定!
最后,由于m_mtx
未锁定,我看不到m_quit = true
中m_thd
的显示方式。
答案 1 :(得分:0)
你遇到的一个问题是std::condition_variable::notify_one
被调用,而等待线程持有的std::mutex
被保持(logger::write
调用logger::fin
时发生)
这会导致通知的线程立即再次阻塞,因此打印机线程可能会在销毁时无限期地阻塞(或直到虚假唤醒)。
在持有与等待线程相同的互斥锁时,绝不应该通知。
来自en.cppreference.com的引用:
通知线程不需要将锁定保持在与等待线程所持有的互斥锁相同的互斥锁上;实际上这样做是一种悲观,因为通知的线程会立即再次阻塞,等待通知线程释放锁。
答案 2 :(得分:0)
对just prior or post to DllMain
getting called分别构造和破坏全局和静态DLL_PROCESS_ATTACH
and DLL_PROCESS_DETACH
.。这个问题是它发生在加载器锁内。如果dealing with kernel objects因为它可能导致死锁,或者应用程序随机崩溃,那么这个星球上最危险的地方是什么。因此,您永远不应该在Windows EVER 上使用线程基元作为静态。因此,处理全局对象的析构函数中的线程基本上就是我们在DllMain
中警告不要做的事情。
该建筑正在拆除。不要打扰地板,清空垃圾桶,擦掉白板。并且不要在建筑物的出口处排队,这样每个人都可以将他们的进/出磁铁移出。你所做的只是让拆迁队等你完成这些毫无意义的清理工作。
如果你的DllMain函数创建了一个线程,然后等待线程做某事(例如,等待线程发出一个事件表明它已经完成初始化,那么你就已经创建了一个死锁.DLL_PROCESS_ATTACH DllMain中的通知处理程序正在等待新线程运行,但新线程无法运行,直到DllMain函数返回,以便它可以发送新的DLL_THREAD_ATTACH通知。
这种死锁在DLL_PROCESS_DETACH中更常见,其中DLL想要关闭其工作线程并等待它们在卸载之前进行清理。你不能在DLL_PROCESS_DETACH中等待一个线程,因为该线程需要在它退出之前发出DLL_THREAD_DETACH通知,直到你的DLL_PROCESS_DETACH处理程序返回它才能执行。
即使使用EXE也会发生这种情况,因为可视化C ++运行时会在运行时加载或卸载时运行C运行时作弊和注册其构造函数和析构函数,从而导致同样的问题:
答案是C运行时库雇用了一个密钥。雇佣的lackey是C运行时库DLL(例如,MSVCR80.DLL)。 EXE中的C运行时启动代码使用C运行时库DLL注册所有析构函数,当C运行时库DLL获取其DLL_PROCESS_DETACH时,它将调用EXE请求的所有析构函数。