带定时器的 C++ 多线程编程

Posted

技术标签:

【中文标题】带定时器的 C++ 多线程编程【英文标题】:C++ multi thread programming with timer 【发布时间】:2016-03-17 20:06:33 【问题描述】:

我是多线程编程的新手,所以这个问题可能看起来有点傻,但我真的需要解决这个问题,这样我才能将它应用到我的项目中(这要复杂得多)。 下面是我的代码,我试图让 2 个线程(父线程和子线程)在它们执行时更新同一个共享定时器,并在定时器达到特定限制时停止。 但是当我编译并执行这段代码时,有两种不同的结果: 1. child 打印“由 child 在 200000 完成”但程序没有退出; 2.child打印“done by child at 200000”并退出后,parent继续执行,打印几十行“parent doing work”和“parent at 190000”,然后打印“done by parent at 200000”和程序正确退出。 我想要的行为是对于更新计时器的任何线程,达到限制并退出,另一个线程也应该停止执行并退出。我想我可能在这里遗漏了一些微不足道的东西,但我已经尝试以多种方式更改代码,但我尝试的任何方法似乎都不起作用。任何帮助将不胜感激:)

#include <iostream>
#include <unistd.h>
#include <mutex>
#include <time.h>

using namespace std;

mutex mtx;

int main () 
  int rc;
  volatile int done = 0;
  clock_t start = clock();
  volatile clock_t now;

  rc = fork();
  if (rc == 0)  //child
    while (true) 
      cout << "child doing work" << endl;
      mtx.lock();
      now = clock() - start;
      if (done) 
        mtx.unlock();
        break;
      
      if (now >= 200000 && !done) 
        done = 1;
        cout << "done by child at " << now << endl;
        mtx.unlock();
        break;
      
      cout << "child at " << now << endl;
      mtx.unlock();
    
    _exit(0);
  
  else  // parent
    while (true) 
      cout << "parent doing work" << endl;
      mtx.lock();
      now = clock() - start;
      if (done) 
        mtx.unlock();
        break;
      
      if (now >= 200000 && !done) 
        done = 1;
        cout << "done by parent at " << now << endl;
        mtx.unlock();
        break;
      
      cout << "parent at " << now << endl;
      mtx.unlock();
    
  
  return 0;

【问题讨论】:

volatile 没有做你认为它使用 std::atomic 的事情。而且你不是在测试线程而是进程 - fork 在你的进程中创建一个新进程而不是一个新线程。 你没有两个线程,而是一个父进程和一个子进程(那是完全不同的)。 请停止使用volatile。对于fork 和进程,您可以阅读beej.us/guide/bgipc for IPC。 那些不是独立的线程,它们是独立的进程 @LongLin 为什么不用std::thread 【参考方案1】:

多进程

您的代码是多进程而不是多线程:fork() 将通过复制调用进程来创建一个新的单独进程。

结果:在复制的那一刻,所有变量在两个过程中都包含相同的值。但是每个进程都有自己的副本,因此在父进程中修改的变量不会在子进程的地址空间中更新,反之亦然。

如果你想在进程之间共享变量,你应该看看this SO question

多线程

对于真正的多线程,你应该使用std::thread。忘记 volatile,因为它不是线程安全的。请改用&lt;atomic&gt;,如awesome video 中所述。

这里是第一次尝试:

#include <iostream>
#include <mutex>
#include <thread>
#include <atomic>
#include <time.h>

using namespace std;

void child (atomic<int>& done, atomic<clock_t>& now, clock_t start)

  while (!done) 
      cout << "child doing work" << endl;
      now = clock() - start;
      if (now >= 2000 && !done) 
          done = 1;
          cout << "done by child at " << now << endl;
      
      cout << "child at " << now << endl;
      this_thread::yield(); 
  


void parent (atomic<int>& done, atomic<clock_t>& now, clock_t start) 

  while (!done) 
      cout << "parent doing work" << endl;
      now = clock() - start;
      if (now >= 2000 && !done) 
        done = 1;
        cout << "done by parent at " << now << endl;
      
      cout << "parent at " << now << endl;
      this_thread::yield(); 
    


int main () 
  atomic<int> done0;
  clock_t start = clock();
  atomic<clock_t> now;

  thread t(child, std::ref(done), std::ref(now), start); // attention, without ref, you get clones
  parent (done, now, start); 
  t.join();  

  return 0;

请注意,您不需要使用互斥锁来保护原子访问,如果您想这样做,建议使用 lock_guard 替代。

这个例子当然比较弱,因为如果你测试一个原子变量 if 条件,它的值可能在进入 if 块时已经改变了。这不会导致您的逻辑出现问题,其中“完成”表示“完成”。但如果您需要更谨慎的方法,compare_exchange_weak()compare_exchange_strong() 可以提供更多帮助。

【讨论】:

感谢您的示例。在阅读上述 cmets 后不久,我确实让它工作了,我记得发表评论说。无论如何,我有一个后续问题。我的解决方案与您的解决方案非常相似。起初,在 main() 中,我将 parent 行放在 child 行的前面,输出表明 parent 总是先执行,而 child 只有在 parent 完成后才能执行;然后我交换了这两条线,它按预期工作。那么,围绕这两条线交换是否真的会影响我提到的执行顺序?如果是这样,为什么?在此先感谢:) 你总是至少有一个线程在执行:主线程。它按顺序执行语句,因此如果遇到对 parent 的调用,它将执行该函数调用。只有当您创建并启动一个线程对象时,才会并行执行第二个线程(尽可能)。因此,如果您创建此对象,则首先要在第二个线程启动时父级必须结束。主线程将礼貌地等待连接发生。以其他顺序启动第二个线程并在主线程执行父线程时执行子线程。 我的评论是日志,但尝试用它制作一个序列图,我认为它会变得更清晰

以上是关于带定时器的 C++ 多线程编程的主要内容,如果未能解决你的问题,请参考以下文章

并发编程之多线程

C++多线程编程

c++ 多线程编程

JavaEE开发之Spring中的多线程编程以及任务定时器详解

Linux C++的多线程编程

《C++多线程编程》学习笔记