打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
Why and how to use Mutex locks
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 8140556785354512 8140556785354512 8140556785354512 8...140556793747216 8140556793747216 8140556793747216 8140556793747216 8...140556785354512 8140556785354512 8140556785354512 8140556785354512 8140556785354512 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 variablepthread_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 8140420583675664 8140420583675664 8140420583675664 8...140420592068368 8140420592068368 8140420592068368 8140420592068368 8
本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
深入Phtread(二):线程的同步
线程假唤醒的原因<good>
pthread_cond_signal()的具体位置?
RAII手法封装互斥锁
Linux互斥锁的使用代码实现
POSIX 线程详解
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服