LinuxQuestions.org
Help answer threads with 0 replies.
Go Back   LinuxQuestions.org > Blogs > TheIndependentAquarius
User Name
Password

Notices

Rate this Entry

Why and how to use Mutex locks - Pthreads

Posted 02-27-2012 at 11:06 AM by TheIndependentAquarius
Updated 05-21-2012 at 01:51 AM by TheIndependentAquarius

Problem:
Underlying operating system may schedule the threads on the basis of some algorithm which works on time limit (threads may be allowed to run only for certain durations of time, thus preventing the other threads from starvation), priority (higher priority threads may interrupt the lower priority threads during their execution), or some other factor.

These factors may cause a thread to sleep before it completes its task fully. Another thread may get scheduled meanwhile and may change the data/file on which the first thread was working.

Example:
Let there be a situation where threadA and threadB both have to write a message to a particular file, one after the other.
  • If threadA gets scheduled first for a particular amount of time and starts writing the message to the file, its execution time may get expired before it completes the message (due to some scheduling algorithms).
    That is threadA may be able to write only half of its message to the file in that particular time.

  • The underlying operating system may now schedule threadB.

  • threadB starts writing its own message to the same file (from the point where threadA's incomplete message ended), and before it completes its chore, threadA may get scheduled again.

  • Now, threadA starts writing the remaining part of its incomplete message (from the point where threadB's complete message ended)!!
The file will now look as follows:
-- Part one of threadA's message
-- Part one of threadB's message
-- Part two of threadA's message
-- Part two of threadB's message
This is a complete mess.

The following code demonstrates the above situation.
Code:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

// `fp` is the file pointer for the file to be edited by both the threads.
FILE *fp;

// This is the function which both the threads are supposed to execute.
void * printHello (void* threadId)
{
	unsigned short iterate;
	for (iterate = 0; iterate < 10000; iterate++)
	{
		// Each thread is supposed to write its thread Id 10000 times (in a row) to this file.
		fprintf (fp, " %lu %lu", pthread_self (), sizeof (pthread_t));
		fprintf (fp, "\n");
	}
	return 0;
}

int main ()
{
	pthread_t arrayOfThreadId [2];
	int       returnValue;

	// This is the file that'll be be shared by two threads.
	fp = fopen ("xyz", "w");
	
	for (unsigned int iterate = 0; iterate < 2; iterate++)
	{
		if ((returnValue = pthread_create (&arrayOfThreadId [iterate],
								    NULL,
								    printHello,
								    (void*) &arrayOfThreadId [iterate])) != 0)
		{
			printf ("\nerror: pthread_create failed with error number %d", returnValue);
		}
	}
	
	for (unsigned int iterate = 0; iterate < 2; iterate++)
		pthread_join (arrayOfThreadId [iterate], NULL);
	
	return 0;
}
Output:
Code:
140556785354512 8
140556785354512 8
140556785354512 8
140556785354512 8
...
140556793747216 8
140556793747216 8
140556793747216 8
140556793747216 8
...
140556785354512 8
140556785354512 8
140556785354512 8
140556785354512 8
140556785354512 8
...
Observation:
Both the threads get scheduled for short durations repeatedly, and therefore cannot write their pids to the file all in one row.
To prevent the file from turning into a kitchen sink, we need to lock it somehow until the scheduled thread completes its message writing task fully.

Solution:
A mutex is a lock (from Pthread library) that guarantees the following three things:
  • Atomicity -
    Locking a mutex is an atomic operation, meaning that the threads library assures you that if you lock a mutex, no other thread can succeed in locking that mutex at the same time.

  • Singularity -
    If a thread managed to lock a mutex, it is assured that no other thread will be able to lock the same mutex until the original thread releases the lock.

  • Non-Busy Wait -
    If threadA attempts to lock a mutex that was locked by threadB, the threadA will get suspended (and will not consume any CPU resources) until the lock is freed by threadB. When threadB unlocks the mutex, then the threadA will wake up and continue execution, having the mutex locked by it.
(Advise: Read the above 3 points five times if you really want to absorb them!)

Mutex lock API (from pthread library):
  • Creating a mutex:
    In order to create a mutex, we first need to declare a variable of type `pthread_mutex_t`, and then initialize it.
    One way to initialize it is by assigning it the `PTHREAD_MUTEX_INITIALIZER` macro.
    Code:
    pthread_mutex_t demoMutex = PTHREAD_MUTEX_INITIALIZER;
    This type of initialization creates a mutex called "fast mutex".
    This means that if a thread locks a mutex and then tries to lock the same mutex again, it'll get stuck in a deadlock.

    Another type of mutex, called "recursive mutex", allows the thread that locked it, to lock it several more times, without getting blocked (but other threads that try to lock the mutex now will get blocked).

    If the thread then unlocks the mutex, it'll still be locked, until it is unlocked the same amount of times as it was locked.
    This kind of mutex can be created by assigning the constant
    `PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP` to a mutex variable.
    Code:
    pthread_mutex_t demoMutex = PTHREAD_RECURSIVE_MUTEX_INITIALIZER_NP;
    Signature:
    Code:
    int pthread_mutex_init (pthread_mutex_t           *mutexVariable, 
    		        const pthread_mutexattr_t *attributes);
    The `pthread_mutex_init ()` function initialises the mutex referenced by `mutexVariable` with attributes specified by `attributes`. If `attributes` is NULL, the default mutex attributes are used. After successful initialisation, the state of the mutex becomes unlocked.
    Attempting to initialise an already initialised mutex results in an undefined behaviour.
  • Locking and unlocking a mutex:
    `pthread_mutex_lock ()` function attempts to lock the mutex, or pause the execution of the currently active thread if the mutex is already locked by some other thread.
    In this case, when the mutex is unlocked by the owner thread, the function will return with the mutex locked now by the new thread.

    Code:
    int pthread_mutex_lock (pthread_mutex_t *myMutex);
    `myMutex` is a pointer towards the mutex variable.

    On success, `pthread_mutex_lock ()` returns 0. On error, one of the following values is returned:
    • EDEADLK:
      The current thread already owns the mutex.
    • EINVAL:
      The mutex was created with the attribute having the value PTHREAD_PRIO_PROTECT and the calling thread's priority is higher than the mutex's current priority ceiling. This means that the operating system won't pause the original thread even if its priority is lower than the new calling thread.
      Or
      The value specified by mutex does not refer to an initialized mutex object.
    • EFAULT:
      Mutex is an invalid pointer.

    `pthread_mutex_lock ()` might block the calling thread for a non-determined duration, in case of the mutex being already locked.
    If it remains locked forever, it is said that the thread is "starved" - it was trying to acquire a esource, but never got it. It is up to the programmer to ensure that such starvation won't occur. The pthread library does not help us with that.

    After the thread completes its task inside the critical region, it should free the mutex using the `pthread_mutex_unlock ()` function:
    Code:
    int pthread_mutex_unlock (pthread_mutex_t *mutex);
    `myMutex` is a pointer towards the mutex variable.

    On success, `pthread_mutex_unlock ()` function shall return zero, otherwise:
    • EINVAL:
      The value specified by mutex does not refer to an initialized mutex object.
    • EPERM:
      The current thread does not own the mutex.

  • Destroying a mutex:
    After all threads finished using a mutex, it should be destroyed using the `pthread_mutex_destroy ()` function:
    Code:
    int pthread_mutex_destroy (pthread_mutex_t *myMutex);
    `myMutex` is a pointer towards the mutex variable.

    On success, `pthread_mutex_lock ()` returns 0. On error, one of the following values is returned:
    • EBUSY:
      An attempt to destroy the mutex while it is locked or referenced (for example, while being used in a `pthread_cond_timedwait ()` or `pthread_cond_wait()`) by another thread.
    • EINVAL:
      The value specified by `myMutex` is invalid.

After this call, myMutex variable should not be used as a mutex any more, unless it is initialized again.

The following code shows how the usage of mutex locks prevents the threads from crossing each other's lines.
Code:
#include <pthread.h>
#include <stdio.h>
#include <unistd.h>

FILE 		   *fp;
// Declaring the mutex variable
pthread_mutex_t demoMutex;

void * printHello (void* threadId)
{
        // Applying the mutex lock where the threads are supposed to write on the common file.
	pthread_mutex_lock (&demoMutex);
	for (unsigned short iterate = 0; iterate < 10000; iterate++)
		fprintf (fp, " %lu %lu\n", pthread_self (), sizeof (pthread_t));
	pthread_mutex_unlock (&demoMutex);

	return 0;
}

int main ()
{
	pthread_t arrayOfThreadId [2];
	int       returnValue;

        // Opening the file which is to be shared between the threads.
	fp = fopen ("xyz", "w");
	
	// Initializing the already declared mutex variable.
	pthread_mutex_init (&demoMutex, NULL);
	
	for (unsigned int iterate = 0; iterate < 2; iterate++)
	{
		if ((returnValue = pthread_create (&arrayOfThreadId [iterate],
								    NULL,
								    printHello,
								    (void*) &arrayOfThreadId [iterate])) != 0)
		{
			printf ("\nerror: pthread_create failed with error number %d", returnValue);
		}
	}
	
	for (unsigned int iterate = 0; iterate < 2; iterate++)
		pthread_join (arrayOfThreadId [iterate], NULL);
	
	return 0;
}
Output:
Code:
140420583675664 8
140420583675664 8
140420583675664 8
140420583675664 8
...
140420592068368 8
140420592068368 8
140420592068368 8
140420592068368 8
Posted in PThreads
Views 10384 Comments 3
« Prev     Main     Next »
Total Comments 3

Comments

  1. Old Comment
    Anisha,

    This was an interesting review of thread dynamics. However, I found myself waiting for you to give some design insights on the (proper) use of threads; you did not.

    Example scenarios are difficult to conceive and I understand your point was to review mute locks; job well done on that point.

    Personally, I try to avoid any scenario which would seem to require multiple threads to use the same resource. Seeing a case where it seems that two threads must share a resource is a clue to me that I have a design problem. One resource, one service thread, and some type of IPC to allow other threads to request the services of the owning thread. No doubt that the IPC selected (AsyncQueues from GLIB) would be using mutex locks, to keep things in order.

    Thanks for taking the time to review this topic.
    James,
    Posted 02-29-2012 at 06:02 PM by skoona skoona is offline
  2. Old Comment
    Thanks for peeping here, James.

    Quote:
    Originally Posted by skoona View Comment
    However, I found myself waiting for you to give some design insights on the (proper) use of threads; you did not.
    TBH I write these blogs for myself, only. I did not expect anyone
    to even notice it.

    I am reveising all these threads concepts now a days, so I will posting all
    that I read here in these(LQ) blogs, sooner or later.

    Quote:
    Originally Posted by skoona View Comment
    Example scenarios are difficult to conceive.
    Yes, I understand. Actually I "assumed" that a person reading about mutex
    locks will already be knowing how to create threads.

    Though I will be definitely be writing the basic threads creating blog
    after a few days.

    Quote:
    Originally Posted by skoona View Comment
    Personally, I try to avoid any scenario which would seem to require multiple threads to use the same resource. Seeing a case where it seems that two threads must share a resource is a clue to me that I have a design problem.
    I am now a days building a remote logging library where several threads
    need to send several messages to several servers, but they have to read their
    messages from a shared structure.
    Posted 02-29-2012 at 06:54 PM by TheIndependentAquarius TheIndependentAquarius is offline
  3. Old Comment
    Anisha,


    Quote:
    I am now a days building a remote logging library where several threads
    need to send several messages to several servers, but they have to read their
    messages from a shared structure.
    That sounds like and interesting design problem. I've done many similar things and should probably start my own blog. Thinking about that is how I found your blog.

    Anyway, These types issues or patterns have been addressed before. Packages like "ActiveMQ" are great tools that can be leveraged against these design problems. If its possible for you to use tools like GLIB in your project, they have some excellent multi-threading APIs to allow more focus on design rather than boiler plate code.

    I recently finished a healthcare related project that required protocol based message exchanges with 23 devices over serial ports. It was multi-threaded and the only mutex like apis used, were used to start or stop the threads. By design the shared resource was several GAsyncQueues (GLIB). In that project, losing a message literally meant someone could loose their life if help did not arrive in time.

    Here is topic for ya. "Detecting that a thread is frozen, or crashed and recovering it without application restart.", is something I'm very interested in. Hope you include this topic in future entries.

    Thanks again.
    Posted 02-29-2012 at 08:40 PM by skoona skoona is offline
 

  



All times are GMT -5. The time now is 02:16 PM.

Main Menu
Advertisement

My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
identi.ca: @linuxquestions
Facebook: linuxquestions Google+: linuxquestions
Open Source Consulting | Domain Registration