Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Thread mutex behaviour

Tags:

c

mutex

I'm learning C. I'm writing an application with multiple threads; I know that when a variable is shared between two or more threads, it is better to lock/unlock using a mutex to avoid deadlock and inconsistency of variables. This is very clear when I want to change or view one variable.

int i = 0; /** Global */
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

/** Thread 1. */
pthread_mutex_lock(&mutex);
i++;
pthread_mutex_unlock(&mutex);

/** Thread 2. */
pthread_mutex_lock(&mutex);
i++;
pthread_mutex_unlock(&mutex);

This is correct, I think. The variable i, at the end of the executions, contains the integer 2.
Anyway, there are some situations in which I don't know exactly where to put the two function calls.

For example, suppose you have a function obtain(), which returns a global variable. I need to call that function from within the two threads. I have also two other threads that call the function set(), defined with a few arguments; this function will set the same global variable. The two functions are necessary when you need to do something before getting/setting the var.

/** (0) */
/** Thread 1, or 2, or 3... */
if(obtain() == something) {

    if(obtain() == somethingElse) {
        // Do this, sometimes obtain() and sometimes set(random number) (1)   
    } else {
        // Do that, just obtain(). (2)
    }

} else {
    // Do this and do that (3)
    // If # of thread * 3 > 10, then set(3*10) For example. (4)
}
/** (5) */

Where I have to lock, and where I have to unlock? The situation can be, I think, even more complex. I will appreciate an exhaustive answer.

Thank you in advance.
—Alberto

like image 973
Donovan Avatar asked Oct 20 '25 04:10

Donovan


1 Answers

Without any protection:

The Operating System may interrupt each of your threads anytime, and give the processor to the other. "Anytime" includes "between two assembly instructions that actually come from the same C command".

Now, suppose your variable occupies 64 bits in a 32-bit processor. That means your variable occupies two processor "words". In order to write it, the processor needs two assembly instructions. Same for reading. If the thread gets interrupted between the two, you get trouble.

In order to give a more clear example, I will use the analogy of two decimal digits to represent the two binary 32-bit words. So say you are incrementing a two-digit decimal number in a 1-digit processor. To increment 19 to 20, you must read 19, do the math, then write 20. In order to write 20, you must write 2 then write 0 (or vice-versa). If you write 2, then get interrupted before writing 0, the number in memory will be 29, far from what would actually be right. The other thread then proceeds to read the wrong number.

Even if you have a single digit, there's still the read-modify-write issue Blank Xavier explained.

With mutex:

When thread A locks the mutex, thread A checks a mutex variable. If it's free, thread A writes it as taken. It does it using an atomic instruction, one assembly instruction, so there is no "in between" to interrupt. It then proceeds to increment 19 to 20. It can still be interrupted during the incorrect 29 variable value, but it's OK, because now nobody else can access the variable. When thread B tries to lock the mutex, it checks the mutex variable, it is taken. So thread B knows it can't touch the variable. It then calls the Operating System, saying "I give up the processor for now". Thread B will repeat that if it gets the processor again. And again. Until thread A finally gets the processor back, finishes what it was doing, then unlocks the mutex.

So, when to lock?

As so many things, it depends. Mostly on the specific behaviour order your application needs to work correctly. You need to always lock before reading or writing to get the protection, then unlock afterwards. But the "locked block of code" may have many commands, or a single one. Keep the dance explained above in mind and think about how your application should behave.

There are also performance issues. If you lock/unlock around every single line of code, you waste time locking/unlocking. If you lock/unlock only around huge blocks of code, then each thread will wait a long time for the other to release the mutex.

Not really "always"

Now, there are some situations in which you may skip the locking-unlocking. They happen when you are dealing with a one-digit (meaning one processor word) variable, and each thread is either only reading it, or only writing it, so the value read will not determine what value to write to it later. Do this only if you are very sure of what you are doing, and really need the performance increase.

like image 171
Emilio M Bumachar Avatar answered Oct 22 '25 20:10

Emilio M Bumachar