0

I began learning about POSIX threads recently, and I've learned that when you have two threads Main and B, thread B can continuously change a variable in thread Main if I reference the variable as the void pointer in thread B's creation.

That lead me to wonder how to make thread Main continuously change a variable in thread B. I wrote a program to test whether changing the sent parameter changes thread B by running thread B and then changing the referenced variable. It didn't do anything. Is this result right?

So basically:

void *someFunc(void *var) {
    int *num=(int*) var;
    int num2=*num;
    while (true) {
        if (num2==1) {
            *num=3;
        } else {
            *num=5;
        }
    }
    return NULL;    
}

someVar=1;
pthread_t threadB;
if(pthread_create(&threadB, NULL, someFunc , &someVar)) {
    return 1;
}
someVar=2;
//then join both threads later and print someVar
//will someVar be 3 or 5?

Basically, when I reference a variable using the void pointer in thread creation, will any future changes to that variable affect the newly created thread? If this is not true, in order to continuously change it, is there some particular call for that? Should I look into locks/mutex or just put someFunc into a class and change its initializer variables? Thanks!

Ulrich Eckhardt
  • 16,572
  • 3
  • 28
  • 55
msun
  • 41
  • 9
  • 1
    It is called [Race Condition](http://stackoverflow.com/questions/34510/what-is-a-race-condition) and it triggers [undefined behavior](https://en.wikipedia.org/wiki/Undefined_behavior) (i.e. it is impossible to say what will happen). Also note that since C++11 there are [standard facilities for multithreading in C++](http://en.cppreference.com/w/cpp/thread) and you no longer need pthreads. – Ivan Aksamentov - Drop Jan 24 '16 at 06:28
  • 1
    In order to better understand [Sequentially Consistent](https://en.wikipedia.org/wiki/Sequential_consistency) Data Race Free Memory Model of C++11, see famous Herb Sutter's talk: [C++ and Beyond 2012: Herb Sutter - atomic<> Weapons](https://channel9.msdn.com/Shows/Going+Deep/Cpp-and-Beyond-2012-Herb-Sutter-atomic-Weapons-1-of-2) – Ivan Aksamentov - Drop Jan 24 '16 at 06:32
  • I've realized that I should actually look into global variables for this type of stuff. Thanks! – msun Jan 24 '16 at 06:33
  • Global variables are still the wrong tool, because they share info with **everything** else. Share a single data structure that contains the data and a mutex to control access to the data. Don't forget to document which data is shared and which data any mutex protects. – Ulrich Eckhardt Jan 24 '16 at 08:47

2 Answers2

1

The line

int num2=*num;

Creates a copy of the number pointed to by the main thread. You have a race, therefore: if it is changed before the copy, one thing will happen; otherwise, the child thread will never see the change.

Ami Tavory
  • 74,578
  • 11
  • 141
  • 185
-3

Because you pass someVar by pointer to someFunc, and then you copy it to the pointer num, any change to someVar will immediately change the value of *num.

But num2 will not be affected by changes to someVar, because num2 is a a different variable allocated don the stack of thread B. Therefore, the outcome of the while loop will be determined by the value that was assigned to num2 when the thread started. This can be either 1 or 2, depending on how fast the main thread and thread B are running. Such a dependency is a non-deterministic behavior called a "race condition", and you need to be very careful to avoid it.

Adi Levin
  • 5,165
  • 1
  • 17
  • 26
  • 1
    Suggesting the use of an unsynchronized variable as replacement for "low level constructs" like "locks and mutexes" is plain nonsense. You have no guarantees when a write operation in one thread becomes visible in another thread with that! Please see the video that @Drop provided a link to in the comments. – Ulrich Eckhardt Jan 24 '16 at 09:39
  • Perhaps I should add some missing explanations here: I have personally done lots and lots of multithreading in C++, using many different mechanisms. The main concerns related to multithreading are the correctness (thread-safety, no deadlock, no race conditions) and the performance. My approach is to only use locking where it is really needed. There are techniques to do safe multithreading without locking (see https://en.wikipedia.org/wiki/Multiversion_concurrency_control). In particular, if all you want is to signal completion of an action, a simple shared pointer is simplest and safe. – Adi Levin Jan 24 '16 at 11:13
  • So you store the results of that action somewhere and then set the flag (I take that's what you mean with shared pointer, not `std::shared_ptr`) that it's done? Write reordering will break this, so I disagree that this is safe. Using atomics can make this work even without locking, true, but it isn't without synchronization either. – Ulrich Eckhardt Jan 24 '16 at 11:52
  • I agree with you that in order to share data, you need to take care, and in many cases you need to synchronize. What I am saying is that for the purpose of signaling completion, there is no need for atomic variables or shared_ptr or interlocked functions. A simple pointer, declared as volatile, is just as good, because it can only change its value from 0 (incomplete) to 1 (complete). – Adi Levin Jan 24 '16 at 11:56
  • 1
    It's amazing how many people still don't know what volatile actually does and think it helps in concurrent programming. [One of the many relevant links on this topic](http://herbsutter.com/2009/01/12/effective-concurrency-volatile-vs-volatile/). To make it short: volatile has no use in multithreaded code. The shown code has a race condition which makes it UB, just as much as reading one past the end of an array, it might work on your compiler and architecture but that's it. – Voo Jan 24 '16 at 13:58
  • Guys - thank you for your great comments. I removed the controversial part of my answer :-) – Adi Levin Jan 24 '16 at 14:08
  • 1
    It's not just non-deterministic, it's undefined behaviour, which is far worse. – Puppy Jan 24 '16 at 14:11