2

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;
}
Long Lin
  • 41
  • 1
  • 5
  • 2
    `volatile` does not do what you think it does use std::atomic<> instead. And you aren't testing threads but processes - `fork` creates a new process not a new thread in your process. – Richard Critten Mar 17 '16 at 20:10
  • 4
    You do not have two threads, but a parent and a child process (that is completely different). –  Mar 17 '16 at 20:10
  • 2
    Please stop using `volatile`. For `fork` and processes you may read http://beej.us/guide/bgipc/ for IPC. – knivil Mar 17 '16 at 20:10
  • Mutexes which are shared between processess need to have their attribute set. Unfortunately, this attribute is not exposed by `std::mutex`, you'd have to obtain it's native handle and use `pthread_mutexattr_setpshared`. – SergeyA Mar 17 '16 at 20:10
  • 1
    Those are not separate threads, those are separate *processes*. – Galik Mar 17 '16 at 20:10
  • thanks for comments, I will rewrite my code with pthread instead – Long Lin Mar 17 '16 at 20:23
  • 2
    @LongLin Why not use `std::thread`? – Galik Mar 17 '16 at 20:28
  • @Galik I am still trying to learn my way through multi threading, would std::thread be a more suitalbe starting point? – Long Lin Mar 17 '16 at 20:35
  • 1
    You betcha. Stick to the standard library where possible. What you learn about it on Unix will apply on Windows, QNX, and anything else you can get a compliant compiler for. – user4581301 Mar 17 '16 at 20:47
  • So now I am doing it with std::thread. I have one main thread and one "child" (created by main) thread. I still want to achieve the goal I described in the question. Which approach would work: checking which thread the current thread is using a if statement and have one separate while loop for each thread to do the job; or having one while loop and check which one is currently executing and have them do different jobs. – Long Lin Mar 17 '16 at 20:59

1 Answers1

6

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.

Community
  • 1
  • 1
Christophe
  • 68,716
  • 7
  • 72
  • 138
  • Thanks for the example. I did get it to work shortly after reading the above comments and I remembered posting a comment saying that. Anyways, I've got a follow up question. My solution has a very similar lay out to yours. At first, in main(), I put the parent line in front of the child line, the outputs suggested that parent always got executed first, and child only got to execute after parent was done; then I swapped those 2 lines, and it worked as expected. So, does swaping around those 2 lines actually affact the order of execution the way I mentioned? If so, why? Thanks in advance :) – Long Lin Mar 18 '16 at 02:34
  • You always have at least one thread that executed: the main thread. It executes the statements in sesuential order, so if it will encounter a call to parent it will execute that function call. Only when you create and initiate a thread object will a decond thread be executed in parallel (as far as possible). So if you create this object, with you first order the parent must be over when the 2nd thread starts. The main thread will wait politely and iddle for the join to occur. With the other order the secnd thread is started and is executing the child when the main thread executes the parent. – Christophe Mar 18 '16 at 06:33
  • My comment is rather log but try to make a sequence diagram out of it and i think it will get clearer – Christophe Mar 18 '16 at 06:36