安全线程终止

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 可以被视为一个吗? 否,因为如果正在执行数据库操作,锁甚至可以防止主线程进入关闭状态。与ifs 不同,如果锁已被占用,则锁获取会阻塞(这里的锁通常表示同步原语)。那么只有在另一个线程释放锁后,第一个线程才会继续。 谢谢,这是处理数据库线程的非常好的解决方案,但没有回答问题。【参考方案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 写的结束,因为我想立即结束这个过程。

以上是关于安全线程终止的主要内容,如果未能解决你的问题,请参考以下文章

如何安全地终止线程? (使用指针)C++

安全线程终止

立即安全地终止线程 (C#)

JAVA笔记(19)--- 线程概述;如何实现多线程并发;线程生命周期;Thread常用方法;终止线程的三种方式;线程安全问题;synchronized 实现同步线程模型;

1.7停止线程

Linux多线程