並行的世界,沒有同步,就失去了秩序,就會亂作一團!試想,交通沒有紅綠燈,生產線產品裝配沒有一定的順序... 結果是顯而易見的。多個線程也需要同步,否則程序運行起來結果不可預測,這是我們最不能容忍的。交通的同步機制就是紅綠燈,Pthread提供了互斥量(mutex)和條件變量(Condition Variables)兩種機制去同步線程。
不變量,臨界區和判定條件
不變量(Invariant):程序所做的一些假設,特別是指變量之間的關係。如:一個queue,有頭節點,和其它數據節點,這些元素之間的連接關係就是不變量。當程序裏面不變量遭受破壞時,後果往往是很嚴重的,輕則數據出錯,重則程序直接崩潰。
臨界區(Critical Section):處理共享數據的一段代碼。
判定條件(Predicates):描述不變量狀態的邏輯表達式。
臨界區(Critical Section):處理共享數據的一段代碼。
判定條件(Predicates):描述不變量狀態的邏輯表達式。
互斥量(Mutex)
一般,多個線程之間都會共享一些數據,當多個線程同時訪問操作這些共享數據時。問題出來了,一個線程正在修改數據時,另外一個可能也去操作這些數據,結果就會變得不一致了。如(gv=0是共享的數據):
線程A:a = gv; gv = a + 10;
線程B: b = gv; gv = a + 100;
線程B: b = gv; gv = a + 100;
可能發生A執行完a=gv(0)時,B開始執行b=gv(0); gv=a+100,此時gv=100,然後a執行gv=a+10,最後gv=10。並不是我們要的結果,我們的想法是兩個線程併發的給gv加上一個值,期望結果110。^_^ 若這是你銀行卡的餘額,若沒有同步,那就慘了(你往卡里打錢,你有個朋友也同時往你卡里匯錢,很有可能餘額只僅加上一方打的)。
互斥量就是爲了解決這種問題而設計的,它是Dijkstra信號量的一種特殊形式。它使得線程可以互斥地訪問共享數據。如:
互斥量就是爲了解決這種問題而設計的,它是Dijkstra信號量的一種特殊形式。它使得線程可以互斥地訪問共享數據。如:
上圖展示了三個線程共享一個互斥量,位於矩形中心線下方的線程鎖定了該互斥量;位於中心線上方且在矩形範圍內的線程等待該互斥量被解鎖,出於阻塞狀態,在矩形外面的線程正常運行。剛開始,mutex是解鎖的,線程1成功將其鎖定,據爲己有,因爲並沒有其它線程擁有它。然後,線程2嘗試去鎖定,發現被線程1佔用,所以阻塞於此,等到線程1解鎖了該mutex,線程2立馬將mutex鎖定。過了會,線程3嘗試去鎖定mutex,由於mutex被鎖定,所以阻塞於此。線程1調用pthread_mutex_trylock嘗試去鎖定個mutex,發現該mutex被鎖定,自己返回繼續執行,並沒有阻塞。繼續線程2解鎖,線程3鎖定成功,最後線程3完成任務解鎖mutex。
創建和銷燬互斥量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int pthread_mutex_init(pthread_mutex_t* mutex, pthread_mutexattr_t* attr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
不要嘗試去使用複製的的mutex,結果未定義。
靜態創建,當mutex以extern或者static存儲時,可以用PTHREAD_MUTEX_INITIALIZER初始化,此時該mutex使用默認屬性。
int pthread_mutex_init(pthread_mutex_t* mutex, pthread_mutexattr_t* attr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
不要嘗試去使用複製的的mutex,結果未定義。
靜態創建,當mutex以extern或者static存儲時,可以用PTHREAD_MUTEX_INITIALIZER初始化,此時該mutex使用默認屬性。
- #include "error.h"
- #include <pthread.h>
- typedef struct my_struct_tag
- {
- pthread_mutex_t mutex;
- int value;
- } my_struct_t;
- my_struct_t data = { PTHREAD_MUTEX_INITIALIZER, 0};
- int main()
- {
- return 0;
- }
動態創建,往往使用mutex時,都會將它和共享數據綁在一起,此時就需要pthread_mutex_init去動態初始化了,記得用完後pthread_mutex_destroy。
- #include "error.h"
- #include <pthread.h>
- typedef struct my_struct_tag
- {
- pthread_mutex_t mutex;
- int value;
- } my_struct_t;
- int main()
- {
- my_struct_t* data;
- int status;
- data = (my_struct_t*)malloc(sizeof(my_struct_t));
- status = pthread_mutex_init(&data->mutex, NULL);
- if(status != 0)
- ERROR_ABORT(status, "pthread_mutex_init");
- pthread_mutex_destroy(&data->mutex);
- free(data);
- return 0;
- }
鎖定和解鎖
原則見上面。
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_trylock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_trylock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
- #include <pthread.h>
- #include <sys/types.h>
- #include "error.h"
- #include <errno.h>
- #define SPIN 10000000
- pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- long counter;
- time_t end_time;
- void* counter_thread(void* arg)
- {
- int status;
- int spin;
- while(time(NULL) < end_time)
- {
- status = pthread_mutex_lock(&mutex);
- if(status != 0)
- ERROR_ABORT(status, "Lock mutex");
- for(spin = 0; spin < SPIN; spin++)
- counter++;
- status = pthread_mutex_unlock(&mutex);
- if(status != 0)
- ERROR_ABORT(status, "Unlock mutex");
- sleep(1);
- }
- printf("Coutner is %#lx/n", counter);
- return NULL;
- }
- void* monitor_thread(void* arg)
- {
- int status;
- int misses = 0;
- while(time(NULL) < end_time)
- {
- sleep(3);
- status = pthread_mutex_trylock(&mutex);
- if(status != EBUSY)
- {
- if(status != 0)
- ERROR_ABORT(status, "Trylock mutex");
- printf("Counter is %ld/n", counter/SPIN);
- status = pthread_mutex_unlock(&mutex);
- if(status != 0)
- ERROR_ABORT(status, "Unlock mutex");
- }else
- misses++;
- }
- printf("Monitro thread missed update %d times./n", misses);
- return NULL;
- }
- int main()
- {
- int status;
- pthread_t pid_counter;
- pthread_t pid_monitor;
- end_time = time(NULL) + 60;
- status = pthread_create(&pid_counter, NULL, counter_thread, NULL);
- if(status != 0)
- ERROR_ABORT(status, "fail to create thread counter");
- status = pthread_create(&pid_monitor, NULL, monitor_thread, NULL);
- if(status != 0)
- ERROR_ABORT(status, "fail to create monitor thread");
- status = pthread_join(pid_counter, NULL);
- if(status != 0 )
- ERROR_ABORT(status, "fail to join counter thread");
- status = pthread_join(pid_monitor, NULL);
- if(status != 0)
- ERROR_ABORT(status, "fail to join monitor thread");
- return 0;
- }
調整mutex大小
mutex應該多大?這裏的大小是相對的,如mutex鎖定到解鎖之間的代碼只有一行,比起有10行的就小了。 原則是:儘可能大,但不要太大(As big as neccessary, but no bigger)。考慮下面的因素:1> mutex並不是免費的,是有開銷的,不要太小了,太小了程序只忙於鎖定和解鎖了。2> mutex鎖定的區域是線性執行的,若太大了,沒有發揮出併發的優越性。
3> 自己掂量1和2,根據實際情況定,或者嘗試着去做。
3> 自己掂量1和2,根據實際情況定,或者嘗試着去做。
使用多個mutex
使用多個mutex一定要注意,防止死鎖(deadlock)發生。下面是一個典型死鎖:
線程A:pthread_mutex_lock(&mutex_a); pthread_mutex_lock(&mutex_b); ...
線程B:pthread_mutex_lock(&mutex_b); pthread_mutex_lock(&mutex_a); ...
存在這種可能,線程A執行了第一句,鎖定了mutex_a;然後線程開始執行第一句鎖定mutex_b;然後他們互相等待解鎖mutex,A等mutex_b被解鎖,B等mutex_a被解鎖,不肯讓步,出於死鎖狀態。
線程B:pthread_mutex_lock(&mutex_b); pthread_mutex_lock(&mutex_a); ...
存在這種可能,線程A執行了第一句,鎖定了mutex_a;然後線程開始執行第一句鎖定mutex_b;然後他們互相等待解鎖mutex,A等mutex_b被解鎖,B等mutex_a被解鎖,不肯讓步,出於死鎖狀態。
- #include <pthread.h>
- #include "error.h"
- #include <time.h>
- pthread_mutex_t mutex_a = PTHREAD_MUTEX_INITIALIZER;
- pthread_mutex_t mutex_b = PTHREAD_MUTEX_INITIALIZER;
- void* thread1(void* arg)
- {
- while(1)
- {
- /*sleep(1);*/
- pthread_mutex_lock(&mutex_a);
- pthread_mutex_lock(&mutex_b);
- printf("[%lu]thread 1 is running! /n", time(NULL));
- pthread_mutex_unlock(&mutex_b);
- pthread_mutex_unlock(&mutex_a);
- }
- return NULL;
- }
- void* thread2(void* arg)
- {
- while(1)
- {
- /*sleep(1);*/
- pthread_mutex_lock(&mutex_b);
- pthread_mutex_lock(&mutex_a);
- printf("[%lu]thread 2 is running! /n",time(NULL));
- pthread_mutex_unlock(&mutex_a);
- pthread_mutex_unlock(&mutex_b);
- }
- return NULL;
- }
- int main()
- {
- pthread_t tid1, tid2;
- int status;
- status = pthread_create(&tid1, NULL, thread1, NULL);
- if(status != 0)
- ERROR_ABORT(status, "thread 1");
- status = pthread_create(&tid2, NULL, thread2, NULL);
- if(status !=0)
- ERROR_ABORT(status, "thread 2");
- status = pthread_join(tid1, NULL);
- if(status != 0)
- ERROR_ABORT(status, "join thread1");
- status = pthread_join(tid2, NULL);
- if(status != 0)
- ERROR_ABORT(status, "join thread2");
- }
解決死鎖的方法:
固定鎖定順序(Fixed locking hierarchy):鎖定mutex的順序固定。
線程A:pthread_mutex_lock(&mutex_a); pthread_mutex_lock(&mutex_b); ...
線程B:pthread_mutex_lock(&mutex_a); pthread_mutex_lock(&mutex_b); ...
嘗試和回退(Try and back off): 鎖定第一個後,嘗試鎖定下一個,若鎖定成功,繼續嘗試下一個,若鎖定失敗,解鎖先去鎖定的。
解鎖順序不會引起死鎖.
- #include <pthread.h>
- #include "error.h"
- #include <errno.h>
- #define ITERATIONS 100
- pthread_mutex_t mutex[3] = {
- PTHREAD_MUTEX_INITIALIZER,
- PTHREAD_MUTEX_INITIALIZER,
- PTHREAD_MUTEX_INITIALIZER
- };
- int backoff = 1;
- int yield_flag = 0;
- void* lock_forward(void* arg)
- {
- int i, iterate, backoffs;
- int status;
- for(iterate = 0; iterate < ITERATIONS; iterate++)
- {
- backoffs = 0;
- for(i = 0; i < 3; i++){
- if(i == 0)
- {
- status = pthread_mutex_lock(&mutex[i]);
- if(status != 0)
- ERROR_ABORT(status,"Lock mutex");
- }else
- {
- if(backoff)
- status = pthread_mutex_trylock(&mutex[i]);
- else
- status = pthread_mutex_lock(&mutex[i]);
- if(status == EBUSY)
- {
- backoff++;
- printf("forward locker backing off at %d./n", i);
- for(; i >= 0; i--)
- {
- status = pthread_mutex_unlock(&mutex[i]);
- if(status != 0)
- ERROR_ABORT(status, "Unlock mutex");
- }
- }else
- {
- if(status != 0)
- ERROR_ABORT(status, "Lock mutex");
- printf("forward locker got %d /n", i);
- }
- }
- if(yield_flag){
- if(yield_flag > 0)
- sched_yield();
- else
- sleep(1);
- }
- }
- printf("lock forward got all locks , %d backoffs/n", backoffs);
- pthread_mutex_unlock(&mutex[2]);
- pthread_mutex_unlock(&mutex[1]);
- pthread_mutex_unlock(&mutex[0]);
- sched_yield();
- }
- return NULL;
- }
- void* lock_backward(void* arg)
- {
- int i, iterate, backoffs;
- int status;
- for(iterate = 0; iterate < ITERATIONS; iterate++)
- {
- backoffs = 0;
- for(i = 2; i >= 0; i--){
- if(i == 2)
- {
- status = pthread_mutex_lock(&mutex[i]);
- if(status != 0)
- ERROR_ABORT(status,"Lock mutex");
- }else
- {
- if(backoff)
- status = pthread_mutex_trylock(&mutex[i]);
- else
- status = pthread_mutex_lock(&mutex[i]);
- if(status == EBUSY)
- {
- backoff++;
- printf("backward locker backing off at %d./n", i);
- for(; i < 3; i++)
- {
- status = pthread_mutex_unlock(&mutex[i]);
- if(status != 0)
- ERROR_ABORT(status, "Unlock mutex");
- }
- }else
- {
- if(status != 0)
- ERROR_ABORT(status, "Lock mutex");
- printf("backward locker got %d /n", i);
- }
- }
- if(yield_flag){
- if(yield_flag > 0)
- sched_yield();
- else
- sleep(1);
- }
- }
- printf("lock backward got all locks , %d backoffs/n", backoffs);
- pthread_mutex_unlock(&mutex[0]);
- pthread_mutex_unlock(&mutex[1]);
- pthread_mutex_unlock(&mutex[2]);
- sched_yield();
- }
- return NULL;
- }
- int main(int argc, char* argv[])
- {
- pthread_t forward, backward;
- int status;
- if(argc > 1)
- backoff = atoi(argv[1]);
- if(argc > 2)
- yield_flag = atoi(argv[2]);
- status = pthread_create(&forward, NULL, lock_forward, NULL);
- if(status != 0)
- ERROR_ABORT(status, "Create forward");
- status = pthread_create(&backward, NULL, lock_backward, NULL);
- if(status != 0)
- ERROR_ABORT(status, "Create backward");
- pthread_exit(NULL);
- }
鎖定鏈
一般用於遍歷數據結果(樹,鏈表),一個用於鎖定指針,一個鎖定數據。
形如:
pthread_mutex_lock(&mutex_a);
pthread_mutex_lock(&mutex_b);
...
pthread_mutex_unlock(&mutex_a)
...
pthread_mutex_unlock(&mutex_b)
注意,鎖定鏈往往會出現大量的鎖定和解鎖操作,有時會得不償失。
形如:
pthread_mutex_lock(&mutex_a);
pthread_mutex_lock(&mutex_b);
...
pthread_mutex_unlock(&mutex_a)
...
pthread_mutex_unlock(&mutex_b)
注意,鎖定鏈往往會出現大量的鎖定和解鎖操作,有時會得不償失。