我有一个场景:
我从dll中启动了一个新线程,可以完成一些工作。
可以在新线程完成其工作之前调用dll析构函数。
如果是这样,我想在析构函数中设置一个布尔标志,告诉线程返回而不是继续。
如果我尝试以下操作,那么我发现因为析构函数被调用而MyDll超出范围,所以 m_cancel 被删除,其值不可靠(有时是假的,有时是真的)所以我不能使用这种方法。
方法1
//member variable declared in header file
bool m_cancel = false;
MyDll:~MyDll()
{
m_cancel = true;
}
//Function to start receiving data asynchronously
void MyDll::GetDataSync()
{
std::thread([&]()
{
SomeFunctionThatCouldTakeAWhile();
if( m_cancel == true )
return;
SomeFunctionThatDoesSomethingElse();
}
}
所以我看了一下这个例子Replacing std::async with own version but where should std::promise live?,其中使用了一个可以从两个线程访问的共享指针。
所以我想我应该:
创建一个指向bool的共享指针,并将其传递给我已经启动的新线程。
在析构函数中,更改此共享指针的值并在新线程中进行检查。
以下是我的想法,但我不确定这是否是解决此问题的正确方法。
方法2
//member variable declared in header file
std::shared_ptr<bool> m_Cancel;
//Constructor
MyDll:MyDll()
{
m_Cancel = make_shared<bool>(false);
}
//Destructor
MyDll:~MyDll()
{
std::shared_ptr<bool> m_cancelTrue = make_shared<bool>(true);
m_Cancel = std::move(m_cancelTrue);
}
//Function to start receiving data asynchronously
void MyDll::GetDataSync()
{
std::thread([&]()
{
SomeFunctionThatCouldTakeAWhile();
if( *m_Cancel.get() == true )
return;
SomeFunctionThatDoesSomethingElse();
}
}
如果我执行上述操作,那么 if(* m_Cancel.get()== true)会导致崩溃(访问冲突)
我是按值传递共享指针还是通过引用传递给std :: thread ??
因为它是一个共享指针,即使MyDll超出范围,std :: thread仍然有效的副本吗?
我该怎么做?
方法3
//Members declared in header file
std::shared_ptr<std::atomic<bool>> m_Cancel;
//Constructor
MyDll:MyDll()
{
//Initialise m_Cancel to false
m_Cancel = make_shared<std::atomic<bool>>(false);
}
//Destructor
MyDll:~MyDll()
{
//Set m_Cancel to true
std::shared_ptr<std::atomic<bool>> m_cancelTrue = make_shared<std::atomic<bool>>(true);
m_Cancel = std::move(m_cancelTrue);
}
//Function to start receiving data asynchronously
void MyDll::GetDataSync()
{
std::thread([=]() //Pass variables by value
{
SomeFunctionThatCouldTakeAWhile();
if( *m_Cancel.get() == true )
return;
SomeFunctionThatDoesSomethingElse();
}
}
我资助的是当析构函数被调用然后 if(* m_Cancel.get()== true)被调用时,它总是崩溃。
我做错了吗?
解决方案
我添加了一个互斥锁,以防止在新线程中检查取消后dtor返回。
//Members declared in header file
std::shared_ptr<std::atomic<bool>> m_Cancel;
std::shared_ptr<std::mutex> m_sharedMutex;
//Constructor
MyDll:MyDll()
{
//Initialise m_Cancel to false
m_Cancel = make_shared<std::atomic<bool>>(false);
m_sharedMutex = make_shared<std::mutex>();
}
//Destructor
MyDll:~MyDll()
{
//Set m_Cancel to true
std::shared_ptr<std::atomic<bool>> m_cancelTrue = make_shared<std::atomic<bool>>(true);
std::lock_guard<std::mutex> lock(*m_sharedMutex);//lock access to m_Cancel
{
*m_Cancel = std::move(cancelTrue);
}
}
//Function to start receiving data asynchronously
void MyDll::GetDataSync()
{
auto cancel = this->m_Cancel;
auto mutex = this->m_sharedMutex;
std::thread([=]() //Pass variables by value
{
SomeFunctionThatCouldTakeAWhile();
std::lock_guard<std::mutex> lock(*mutex);//lock access to cancel
{
if( *cancel.get() == true )
return;
SomeFunctionThatDoesSomethingElse();
}
}
}
答案 0 :(得分:3)
第2步是错的。这是一个设计错误。
你的第一个机制并不是因为一个简单的原因而起作用的。 m_cancel==false
可以由编译器优化。当析构函数返回时,m_cancel
不再存在,并且析构函数中的语句不依赖于该写入。析构函数返回后,访问以前保存m_cancel
的内存将是Undefined Behavior。
第二种机制(全局)因更复杂的原因而失败。显而易见的问题是,您只有一个全局m_Cancel
(BTW,m_
对于某些不属于某个成员的内容来说是一个非常具有误导性的前缀。但假设您只有一个MyDll
,它仍然可能因线程原因而失败。你想要的不是shared_ptr
而是std::atomic<bool>
。这对于从多个线程访问是安全的
[编辑]
并且您的第三个机制失败,因为[=]
从封闭范围中捕获名称。 m_Cancel
不在该范围内,但this
是。this
。您不希望该帖子的this
副本,因为auto cancel = this->m_Cancel; std::thread([cancel](...
将被销毁。解决方案:m_cancel
[编辑2]
我认为你真的应该阅读基础知识。在版本3的dtor中,您确实更改了*m_cancel
的值。也就是说,你改变了指针。您应该更改{{1}},即它指向的内容。正如我在上面指出的那样,线程有一个指针的副本。如果更改原始指针,则线程将继续指向旧值。 (这与智能指针无关,哑指针表现相同)。