執行緒同步--遞迴鎖 非遞迴鎖
一、簡介
1.1 程序/執行緒同步方法
常見的程序/執行緒同步方法有互斥鎖(或稱互斥量Mutex)、讀寫鎖(rdlock)、條件變數(cond)、訊號量(Semophore)等。
在windows系統中,臨界區(Critical Section)和事件物件(Event)也是常用的同步方法。
1.2 遞迴鎖/非遞迴鎖
Mutex可以分為遞迴鎖(recursive mutex)和非遞迴鎖(non-recursive mutex)。 遞迴鎖也叫可重入鎖(reentrant mutex),非遞迴鎖也叫不可重入鎖(non-reentrant mutex)。
二者唯一的區別
同一個執行緒可以多次獲取同一個遞迴鎖,不會產生死鎖。
如果一個執行緒多次獲取同一個非遞迴鎖,則會產生死鎖。
Windows下的Mutex和Critical Section是可遞迴的。
Linux下的pthread_mutex_t鎖是預設是非遞迴的。可以通過設定PTHREAD_MUTEX_RECURSIVE屬性,將pthread_mutex_t鎖設定為遞迴鎖。
二、程式碼
2.1 Critical Section遞迴鎖
- #include <Windows.h>
- #include <iostream>
- #include <string>
- int counter = 0;
- CRITICAL_SECTION g_cs;
- void doit(void* arg)
- {
- int i, val;
- for (i=0; i<5000; i++)
- {
- EnterCriticalSection(&g_cs);
- EnterCriticalSection(&g_cs);
- val = counter;
- printf("thread %d : %d\n", int(arg), val+1);
- counter = val + 1;
- LeaveCriticalSection(&g_cs);
- LeaveCriticalSection(&g_cs);
- }
- }
- int main(int argc, char*argv[])
- {
- InitializeCriticalSection(&g_cs);
- HANDLE hThread1 = CreateThread(NULL,0, (LPTHREAD_START_ROUTINE)doit, (void*)1, 0, NULL);
- HANDLE hTrehad2 = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)doit, (void*)2, 0, NULL);
- WaitForSingleObject(hThread1, INFINITE);
- WaitForSingleObject(hTrehad2, INFINITE);
- DeleteCriticalSection(&g_cs);
- return 0;
- }
結果:加1次鎖和2次鎖,均可以正確的輸出1~10000。
2.2 pthread_mutex_t非遞迴鎖
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- int counter = 0;
- pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
- void* doit(void*)
- {
- int i, val;
- for (i=0; i<5000; i++)
- {
- pthread_mutex_lock(&g_mutex);
- pthread_mutex_lock(&g_mutex);
- val = counter;
- printf("%x: %d\n", pthread_self(), val+1);
- counter = val + 1;
- pthread_mutex_unlock(&g_mutex);
- pthread_mutex_unlock(&g_mutex);
- }
- }
- int main(int argc, char*argv[])
- {
- pthread_t tid1, tid2;
- pthread_create(&tid1, NULL, doit, NULL);
- pthread_create(&tid2, NULL, doit, NULL);
- pthread_join(tid1, NULL);
- pthread_join(tid2, NULL);
- return 0;
- }
結果:加1次鎖,可以正確的輸出1~10000;加2次鎖,死鎖,不輸出任何資訊。
2.3 pthread_mutex_t遞迴鎖(PTHREAD_MUTEX_RECURSIVE)
- #include <stdio.h>
- #include <stdlib.h>
- #include <pthread.h>
- int counter = 0;
- pthread_mutex_t g_mutex;// = PTHREAD_MUTEX_INITIALIZER;
- void* doit(void*)
- {
- int i, val;
- for (i=0; i<5000; i++)
- {
- pthread_mutex_lock(&g_mutex);
- pthread_mutex_lock(&g_mutex);
- val = counter;
- printf("%x: %d\n", pthread_self(), val+1);
- counter = val + 1;
- pthread_mutex_unlock(&g_mutex);
- pthread_mutex_unlock(&g_mutex);
- }
- }
- int main(int argc, char*argv[])
- {
- //create recursive attribute
- pthread_mutexattr_t attr;
- pthread_mutexattr_init(&attr);
- //set recursive attribute
- pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
- pthread_mutex_init(&g_mutex, &attr);
- pthread_t tid1, tid2;
- pthread_create(&tid1, NULL, doit, NULL);
- pthread_create(&tid2, NULL, doit, NULL);
- pthread_join(tid1, NULL);
- pthread_join(tid2, NULL);
- pthread_mutex_destroy(&g_mutex);
- //destroy recursive attribute
- pthread_mutexattr_destroy(&attr);
- return 0;
- }
結果:加1次鎖和2次鎖,均可以正確的輸出1~10000。、
線上程同步中,使用鎖是一種非常常見的做法,儘量使用非遞迴鎖,避免使用遞迴鎖!
非遞迴鎖的邏輯清晰,在出現死鎖的時候可以輕鬆DEBUG!僅憑著一點就需要使用非遞迴鎖!