0

Here is the code

  struct val {
  int x;
  int y;
  };

  struct memory {
  struct val *sharedmem;
  };

  struct val va;
  struct memory mem; 

  void *myThreadFun(void *vargp)
  {
  va.x = 1;
  va.y = 2;

  mem.sharedmem = &va;
  sleep(1);
  printf("Printing GeeksQuiz from Thread0 x = %d y = %d sharedmem = %d\n", va.x, va.y, mem.sharedmem->x);
  return NULL;
  }

  int main()
  {
   pthread_t tid;
   printf("Before Thread \n");
   asm volatile ("" ::: "eax", "esi");
   pthread_create(&tid, NULL, myThreadFun, NULL);

   asm volatile ("mfence" ::: "memory");
   printf("sharedmem = %d\n", mem.sharedmem->x); <----- **Here is the fault**
   pthread_join(tid, NULL); 
   printf("After Thread \n");
   exit(0);
  }

When looking at the dmesg it shows 40073f as the fault address, which is

  mov eax,DWORD PTR [rax]

I tried adding few general purpose registers to the clobbered registers but still no success. The code works perfectly fine when run under gdb.

ashish
  • 150
  • 2
  • 11
  • Do you really want to return `null` for a void return type on `myThreadFun ` function? – Parth Sane Apr 15 '17 at 02:58
  • @Boggartfly: The function returns `void *`, and NULL is acceptable and quite commonly used. – Jonathan Leffler Apr 15 '17 at 03:06
  • Hmm, excuse me for my ignorance but it feels absolutely unnecessary if its return type is `void`. – Parth Sane Apr 15 '17 at 03:09
  • You've not formally checked the return values from `pthread_create()` or `pthread_join()`. Does that make any difference? I'm not convinced your embedded assembler makes any difference. The main thread launches and waits for the child thread to complete. What do you get from the thread's `printf()` call? – Jonathan Leffler Apr 15 '17 at 03:09
  • @Boggartfly: Are we looking at the same function? I see `void *myThreadFun(void *vargp)` which says "returns a `void *`" to me. – Jonathan Leffler Apr 15 '17 at 03:10
  • 3
    There is nothing stopping the 'main' thread from accessing `mem` before the newly created thread sets it. This is pretty much the definition of a race condition. – David Wohlferd Apr 15 '17 at 03:12
  • @DavidWohlferd: yup — that'll do it. – Jonathan Leffler Apr 15 '17 at 03:14

1 Answers1

1

To expand a bit on my comment:

When writing a single threaded application, you can be certain that the earlier parts of your code will (effectively) always be executed before the later parts of your code.

But things get tricky when you have more than one thread. How do you know which order things will happen in then? Will the thread start quickly? Or slowly? Will the main thread keep processing? Or will the OS schedule some other thread to run next allowing the 'new' thread to race ahead?

This type of 'race' between 2 (or more) threads to see who gets to a certain point in the code first is a common problem when writing multi-threaded applications, and is called a 'race condition.' These types of bugs are notoriously difficult to track down.

If you don't take any steps to ensure the ordering of events, you are just 'hoping' they happen in the right order. And given the natural variation of how threads get scheduled, there is going to be some wiggle room. Unless you use some of the OS's synchronization functions to coordinate between the threads.

In this case, the event we are looking at is setting the mem.sharedmem variable. This is being done in the myThreadFun thread. Now, will the myThreadFun thread be able to set the variable before the main thread tries to access it? Well, it's hard to say for sure. Might be. Might not.

Will running this under the debugger change how fast the two threads run? You bet it does. How? Hard to predict.

However, if you change your code to join the thread before you try to access the variable, you know for sure that the thread has completed its work and the variable is ready to be used. join is one of those synchronization methods I was referring to.

Looking at your code, perhaps you assume the mfence or memory clobbers will perform this type of synchronization for you? Sorry, but no, it's not that easy.

Learning how to safely synchronize work between multiple threads is a finicky and challenging area of programming. Important, useful, but challenging.


edit1:

One more point I should make is that lfence, sfence and mfence are pretty low-level instructions. People (especially multi-threading newbies) will find that using the atomic functions (which automatically use the appropriate assembler instructions as needed) along with operating system synchronization objects will find their life much easier.

There are lots more tutorials and discussions about atomics than mfence. It also tends to be more portable as well.

David Wohlferd
  • 7,110
  • 2
  • 29
  • 56
  • Actually I was trying to understand how memory barriers work in case of shared variable in multithreaded code. A full barrier finishes all the load/store operation before the barrier. Can you please elaborate what is happening in this case? – ashish Apr 15 '17 at 04:04
  • 1
    "Can you please elaborate what is happening in this case?" - Well, what is happening is that the main thread launches the thread, but before the new thread can get to the point where it sets the value, the main thread has already tried to read it. The `mfence` barrier isn't intended to try to stop one thread from proceeding until something happens on another thread (which is a good thing. How would it know *which* store being performed by *which* thread to wait on?). There is no simple explanation of `mfence`, but perhaps [this](http://stackoverflow.com/a/27635527/2189500) can help? – David Wohlferd Apr 15 '17 at 04:25
  • https://pastebin.com/ekPKehCJ can it be modified to work correctly or we have to use locks? – ashish Apr 15 '17 at 05:56
  • Do the printing after the join? – Jonathan Leffler Apr 15 '17 at 06:24
  • The original code can be 'fixed' by removing all the asm, Sleeps and the call to exit (what is that doing there?). All you need to do is move the `join` before the printf. – David Wohlferd Apr 15 '17 at 06:53
  • The pastebin is very different code (so technically this is a separate question). Instead of assigning the value on one thread and reading it on another, it is assigning and reading on the same thread. You have 100 threads doing the assignment at the ~same time, but they are all assigning the same value to the same memory locations, so I don't see a problem here. OTOH, when you create the threads, you are assigning all the thread ids to the same variable (`tid`). AAR, only the last tid is actually available when you start trying to do the joins. This probably doesn't do what you intend. – David Wohlferd Apr 15 '17 at 06:55