安全线程终止
Posted
技术标签:
【中文标题】安全线程终止【英文标题】:Safe thread termination 【发布时间】:2013-04-11 11:46:30 【问题描述】:工作线程处理数据库更新。当应用程序结束时,我想终止它但不让它离开数据库不一致。所以我在每个重要的数据库操作之前使用标志。一段时间后,代码中充满了 if :
void MyWorkerThread::RunMethod()
if (false == m_Manager->GetShutdownFlag())
DoSomethingOnDB();
if (false == m_Manager->GetShutdownFlag())
DoSomethingMoreOnDB();
...
那么,有没有其他方法可以在不发送大量 if 的情况下处理关机?
【问题讨论】:
【参考方案1】:您最好通过一些同步对象(事件、锁、互斥锁、信号量、临界区等)等来同步线程,以便主线程可以等待工作线程。
这里有一个竞态条件:如果在评估 if
条件之后和执行 DB 操作之前立即引发关闭标志怎么办?
这是一个使用互斥锁作为众所周知的同步原语的示例,但还有更好的方法。
主线程:
int main()
... wait for signal to exit the app
// the DB operations are running on another thread
...
// assume that we start shutdown here
// also assume that there is some global mutex g_mutex
// following line blocks if mutex is locked in worker thread:
std::lock_guard<std::mutex> lock(g_mutex);
Cleanup(); // should also ensure that worker is stopped
工作线程:
void MyWorkerThread::RunMethod()
std::lock_guard<std::mutex> lock(g_mutex);
DoSomethingOnDB();
// some other, non locked execution which doesn't prevent
// main thread from exiting
...
std::lock_guard<std::mutex> lock(g_mutex);
DoSomethingMoreOnDB();
显然你不想重复所有的锁定,你应该把它包装起来:
void MyWorkerThread::RunMethod()
Execute(DoSomethingOnDB);
...
Execute(DoSomethingMoreOnDB);
void MyWorkerThread::Execute(DatabaseFn fn)
std::lock_guard<std::mutex> lock(g_mutex);
fn();
【讨论】:
但是使用锁就不一样了? GetShutdownFlag 可以被视为一个吗? 否,因为如果正在执行数据库操作,锁甚至可以防止主线程进入关闭状态。与if
s 不同,如果锁已被占用,则锁获取会阻塞(这里的锁通常表示同步原语)。那么只有在另一个线程释放锁后,第一个线程才会继续。
谢谢,这是处理数据库线程的非常好的解决方案,但没有回答问题。【参考方案2】:
您的代码中有一个模式,这意味着您可以将其抽象为一个函数:
void MyWorkerThread::execDBCommand(auto cmd)
if (! m_Manager->GetShutdownFlag())
cmd();
else throw std::runtime_error("shutdown"); // or a custom exception
在RunMethod
中为该异常添加一个外部try...catch
,以便更直接地中断处理。
然而,关闭标志也许可以更好地处理为线程的信号,然后回滚任何待处理的事务并中止处理,而不必在每条指令之前检查状态。您还必须在 DB 调用期间屏蔽信号。
这当然取决于你的程序架构。
【讨论】:
【参考方案3】:您的应用程序中是否已有 ReaderWriter 锁?您应该将数据库关闭视为写入操作,并在启动之前获取写入器锁。然后,一旦关闭正在进行,其他读取(获得读取器锁)或写入操作(获得写入器锁)都无法启动。
【讨论】:
不幸的是,它是繁重的遗留代码,据我所知,它不包含任何这些。在我的方法中实现这些将与当前情况非常相似。【参考方案4】:如果你的 Do 函数返回了一些你可以然后 && 或 ||将它们放在一起,以便一旦其中一个返回“false”或“true”(无论哪个是终止条件),您就会停止处理。
如果关闭标志在您的操作期间(或在执行之前?)被不同的线程设置会发生什么,如果您仍然运行此进程会发生什么。
【讨论】:
抱歉,这会使代码的可读性更差。特别是当我想添加一些 cmets、调试行等时。【参考方案5】:您的标志使用将仅限于使用数据库的各种方法:DoSomethingOnDB、DoSomethingMoreOnDB ...
假设您正确使用互斥锁和锁(这里似乎没有),您可以将与数据库相关的方法收集到一个复合方法中,并将您的数据库锁定在那里,并在该单一方法中执行一个 check bool 操作,以便只有一个线程一次进行一个数据库操作。
【讨论】:
我不想一次做所有的改变,所以我有不同的方法 但是所有的数据库方法都是根据m_Manager->GetShutdownFlag()来操作的,为什么不把它们集中到一个地方呢? 想象一下 5 种不同的方法总共有 1 亿次写入。在执行期间终止消息来了。一种方法已经完成(它有 1 个写操作 :P ),它对于数据库一致性是完全正确的。但我不想等到下一个无数 -1 写的结束,因为我想立即结束这个过程。以上是关于安全线程终止的主要内容,如果未能解决你的问题,请参考以下文章
JAVA笔记(19)--- 线程概述;如何实现多线程并发;线程生命周期;Thread常用方法;终止线程的三种方式;线程安全问题;synchronized 实现同步线程模型;