5

I am reading Programming With POSIX Threads (by David Butenhof), and he mentions by using pthread library:

Whatever memory values a thread can see when it unlocks a mutex, either directly or by waiting on a condition variable, can also be seen by any thread that later locks the same mutex. Again, data written after the mutex is unlocked may not necessarily be seen by the thread that locks the mutex, even if the write occurs before the lock.

All a sudden, I wonder whether the following code is valid:

Thread A:

strcpy(buffer, "hello world");
pthread_spin_lock(&lock); // assuming the mutex in the statement above can be interchanged with spinlock. I don't see why it can't
pthread_spin_unlock(&lock);

Thread B:

pthread_spin_lock(&lock);
pthread_spin_unlock(&lock);
// read buffer; assuming thread B has a copy of the pointer somehow

My question is: can thread B see "hello world" in buffer? Based on his statement, it should. I understand the "usual" way is to protect the shared "resource" by lock. But let's assume strcpy() happens at a random time and it can only happen once in the life time of the program, and let's assume thread B somehow calls pthread_spin_lock() after thread A calls pthread_spin_unlock() :)

Side question: is there a faster way of making the change to buffer visible to other threads? Let's say portability isn't an issue and I am on CentOS. One alternative I can think of is using mmap() but not sure whether any change is global visible without using pthread library.

ams
  • 24,923
  • 4
  • 54
  • 75
Hei
  • 1,844
  • 3
  • 21
  • 35
  • 1
    I'd think the mmap() notion would work as it allows different processes to see the same memory, let alone threads. – Jiminion Aug 23 '13 at 13:01
  • @Jim thanks for your reply. Hmm...mmap() probably works with MAP_SHARED. Not sure whether there is an even faster way. – Hei Aug 23 '13 at 13:05

1 Answers1

1

I don't think you've understood this correctly: the locks do not magically transmit the data, they are a form of communication between threads that allows you move data safely.

In your question, you make a lot of assumptions. In fact everything that you write would equally true if the locks didn't exist, as long as your assumptions all hold. The problem with concurrent programming is that, in general, you cannot make assumptions like that.

If thread A makes a change to memory then it becomes visible to thread B instantly (allowing for the vagaries of caching and compiler optimizations). This is always true, with or without locks. But, without the locks, there are no guarantees that the write is complete yet, or even begun.

The quote you have first assumes (requires) that you only write to shared data with a lock set. The last part tells you what bad things can happen if you try to write without locking first.

I'm not sure what "even if the write occurs before the lock" means, precisely, but it's probably referring to the various race conditions and memory caching effects that plague concurrent programs. The locks may not actually transmit the data, but they will be coded such that they force the compiler to synchronise memory across the call (a "memory barrier").

ams
  • 24,923
  • 4
  • 54
  • 75
  • I understand that I can't make any assumption but can only base on the guarantees of pthread (or other thread library) to code. My post was trying to help me understand the guarantees better. – Hei Aug 24 '13 at 02:15
  • And the reason why I can guarantee that the write done by thread A is complete before thread B's read is that thread B won't attempt to read until thread A signals thread B to do so. However, when I say "signal", I don't mean pthread_cond_signal or any kind of signal that involves memory barrier. The signal can be done through pipe, tcp socket, etc (yes, sounds dumb...but I have my own reason). – Hei Aug 24 '13 at 02:18
  • Yes, there are many ways to do signals, but you *do* need a memory barrier of some kind to ensure memory consistency. Luckily, calling an external function is usually enough to do that, so it happens naturally. Without the barrier, the compiler is free to hold data you thought you wrote to memory in registers, or reorder expressions it thinks are unrelated. In fact, the compiler is free to completely rewrite your entire program as long as you can't tell from the outside; unfortunately that can break threads if you don't take a few precautions. – ams Aug 27 '13 at 09:31
  • Right. I believe that when the compiler sees a memory barrier (e.g. pthread_spin_lock), the compiler has to make sure all the memory (load/store) access before the barrier have to be complete first, right? And then, pthread_spin_lock() and pthread_spin_unlock() in thread B have to complete either before pthread_spin_lock() or after pthread_spin_unlock() in thread A, right? If pthread_spin_lock() and pthread_spin_unlock() in thread B complete after pthread_spin_unlock() in thread A, then pthread guarantees that buffer is visible to thread B right after pthread_spin_unlock() in thread B right? – Hei Aug 27 '13 at 11:25
  • That's exactly what a memory barrier means. There now requirement for pthread_spin_lock() to occur first in either thread. The only rule is that *only* one of them must win, and the loser can't return from pthread_spin_lock() until the winner has called pthread_spin_unlock(). – ams Aug 27 '13 at 13:08
  • 1
    So I guess the answer to my very first post is "yes"? :) – Hei Aug 27 '13 at 22:50