Long Lin
Long Lin

Reputation: 41

C++ multi thread programming with timer

I am new to multi thread programming, so this question might seem a little silly, but I really need to work this out so I can apply it to my project (which is way more complicated). Follow is my code, I am trying to have 2 threads (parent and child) to update the same shared timer as they execute and stop when the timer reaches a specific limit. But when I compile and execute this follow piece of code, there are 2 different outcomes: 1. child prints "done by child at 200000" but the program does not exit; 2. after child prints "done by child at 200000" and exits, parent keeps executing, prints a couple of dozen lines of "parent doing work" and "parent at 190000", then prints "done by parent at 200000" and the program exits properly. The behavior I want is for whichever thread that updates the timer, hits the limit and exits, the other thread should stop executing and exit as well. I think I might be missing something trivial here, but I've tried changing the code in many ways and nothing I tried seem to work. Any help will be much appreciated :)

#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;
}

Upvotes: 2

Views: 4351

Answers (1)

Christophe
Christophe

Reputation: 73366

Multi-processes

Your code is multi-processes and not multi-threading: fork() will create a new separate process by duplicating the calling process.

The consequence: At the moment of the duplication, all the variables contain the same value in both processes. But each process has its own copy, so a variable modified in the parent will not be updated in the child's address space an vice-versa.

If you want to share variables between processes, you should have a look at this SO question

Multithread

For real multithreading, you should use std::thread. And forget about volatile, because it's not thread safe. Use <atomic> instead, as explained in this awesome video.

Here a first try:

#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> done{0};
  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;
}

Note that you don't need to protect atomic accesses with a mutex, and that if you want to do, lock_guard would be recommended alternative.

This example is of course rather weak, because if you test an atomic variable if the if-condition, it's value might already have changed when entering the if-block. This doesn't cause a problem in your logic where "done" means "done". But if you'd need a more cauthious approach,
compare_exchange_weak() or compare_exchange_strong() could help further.

Upvotes: 6

Related Questions