1、總體大綱linux
2、線程同步數據庫
1. 同步概念編程
所謂同步,即同時起步,協調一致。不一樣的對象,對「同步」的理解方式略有不一樣。如,設備同步,是指在兩個設備之間規定一個共同的時間參考;數據庫同步,是指讓兩個或多個數據庫內容保持一數據結構
致,或者按須要部分保持一致;文件同步,是指讓兩個或多個文件夾裏的文件保持一致等等。多線程
而編程中、通訊中所說的同步與生活中你們印象中的同步概念略有差別。「同」字應是指協同、協助、互相配合。主旨在協同步調,按預約的前後次序運行。併發
2. 線程同步ide
(1)線程同步概念函數
同步即協同步調,按預約的前後次序運行。post
線程同步,指一個線程發出某一功能調用時,在沒有獲得結果以前,該調用不返回。同時其它線程爲保證數據一致性,不能調用該功能。測試
舉例1: 銀行存款 5000。櫃檯,折:取3000;提款機,卡:取 3000。剩餘:2000
舉例2: 內存中100字節,線程T1欲填入全1, 線程T2欲填入全0。但若是T1執行了50個字節失去cpu,T2執行,會將T1寫過的內容覆蓋。當T1再次得到cpu繼續 從失去cpu的位置向後寫入1,當執
行結束,內存中的100字節,既不是全1,也不是全0。
產生的現象叫作「與時間有關的錯誤」(time related)。爲了不這種數據混亂,線程須要同步。
「同步」的目的,是爲了不數據混亂,解決與時間有關的錯誤。實際上,不只線程間須要同步,進程間、信號間等等都須要同步機制。
所以,全部「多個控制流,共同操做一個共享資源」的狀況,都須要同步。
(2)數據混亂緣由
1)資源共享(獨享資源則不會)
2)調度隨機(意味着數據訪問會出現競爭)
3)線程間缺少必要的同步機制。
以上3點中,前兩點不能改變,欲提升效率,傳遞數據,資源必須共享。只要共享資源,就必定會出現競爭。只要存在競爭關係,數據就很容易出現混亂。
因此只能從第三點着手解決。使多個線程在訪問共享資源的時候,出現互斥。
3. 實現線程同步
(1)互斥量mutex
Linux中提供一把互斥鎖mutex(也稱之爲互斥量)。
每一個線程在對資源操做前都嘗試先加鎖,成功加鎖才能操做,操做結束解鎖。
資源仍是共享的,線程間也仍是競爭的,
但經過「鎖」就將資源的訪問變成互斥操做,然後與時間有關的錯誤也不會再產生了。
但應注意:同一時刻,只能有一個線程持有該鎖。
當A線程對某個全局變量加鎖訪問,B在訪問前嘗試加鎖,拿不到鎖,B阻塞。C線程不去加鎖,而直接訪問該全局變量,依然可以訪問,但會出現數據混亂。
因此,互斥鎖實質上是操做系統提供的一把「建議鎖」(又稱「協同鎖」),建議程序中有多線程訪問共享資源的時候使用該機制。但,並無強制限定。
所以,即便有了mutex,若是有線程不按規則來訪問數據,依然會形成數據混亂。
1)主要函數
pthread_mutex_init函數 pthread_mutex_destroy函數 pthread_mutex_lock函數 pthread_mutex_trylock函數 pthread_mutex_unlock函數 以上5個函數的返回值都是:成功返回0, 失敗返回錯誤號。 pthread_mutex_t 類型,其本質是一個結構體。爲簡化理解,應用時可忽略其實現細節,簡單當成整數看待。 pthread_mutex_t mutex; 變量mutex只有兩種取值一、0。
初始化一個互斥鎖(互斥量) ---> 初值可看做1
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
參1:傳出參數,調用時應傳 &mutex
restrict關鍵字:只用於限制指針,告訴編譯器,全部修改該指針指向內存中內容的操做,只能經過本指針完成。不能經過除本指針之外的其餘變量或指針修改。
參2:互斥量屬性。是一個傳入參數,一般傳NULL,選用默認屬性(線程間共享)。 參APUE.12.4同步屬性
靜態初始化:若是互斥鎖 mutex 是靜態分配的(定義在全局,或加了static關鍵字修飾),能夠直接使用宏進行初始化。e.g. pthead_mutex_t muetx = PTHREAD_MUTEX_INITIALIZER;
動態初始化:局部變量應採用動態初始化。e.g. pthread_mutex_init(&mutex, NULL)
銷燬一個互斥鎖
int pthread_mutex_destroy(pthread_mutex_t *mutex);
加鎖。可理解爲將 mutex--(或-1)
int pthread_mutex_lock(pthread_mutex_t *mutex);
解鎖。可理解爲將mutex ++(或+1)
int pthread_mutex_unlock(pthread_mutex_t *mutex);
嘗試加鎖
int pthread_mutex_trylock(pthread_mutex_t *mutex);
2)加鎖與解鎖
lock與unlock:
lock嘗試加鎖,若是加鎖不成功,線程阻塞,阻塞到持有該互斥量的其餘線程解鎖爲止。
unlock主動解鎖函數,同時將阻塞在該鎖上的全部線程所有喚醒,至於哪一個線程先被喚醒,取決於優先級、調度。默認:先阻塞、先喚醒。
例如:T1 T2 T3 T4 使用一把mutex鎖。T1加鎖成功,其餘線程均阻塞,直至T1解鎖。T1解鎖後,T2 T3 T4均被喚醒,並自動再次嘗試加鎖。
可假想mutex鎖 init成功初值爲1。 lock 功能是將mutex--。 unlock將mutex++
lock與trylock:
lock加鎖失敗會阻塞,等待鎖釋放。
trylock加鎖失敗直接返回錯誤號(如:EBUSY),不阻塞。
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <pthread.h> 4 #include <string.h> 5 6 pthread_mutex_t mutex; 7 8 void *thr(void *arg) 9 { 10 while(1) 11 { 12 pthread_mutex_lock(&mutex); 13 printf("hello world\n"); 14 sleep(30); 15 pthread_mutex_unlock(&mutex); 16 } 17 18 return NULL; 19 } 20 21 int main() 22 { 23 pthread_mutex_init(&mutex, NULL); 24 pthread_t tid; 25 pthread_create(&tid, NULL, thr, NULL); 26 27 sleep(1); 28 29 while (1) 30 { 31 int ret = pthread_mutex_trylock(&mutex); 32 if (ret > 0) 33 { 34 printf("ret = %d, errmsg = %s\n", ret, strerror(ret)); 35 } 36 sleep(1); 37 } 38 39 return 0; 40 }
3)加鎖步驟測試
看以下程序:該程序是很是典型的,因爲共享、競爭而沒有加任何同步機制,致使產生於時間有關的錯誤,形成數據混亂:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <pthread.h> 4 #include <stdlib.h> 5 6 void *thr1(void *arg) 7 { 8 while(1) 9 { 10 printf("hello"); 11 sleep(rand()%3); 12 printf("world\n"); 13 sleep(rand()%3); 14 } 15 } 16 17 void *thr2(void *arg) 18 { 19 while(1) 20 { 21 printf("HELLO"); 22 sleep(rand()%3); 23 printf("WORLD\n"); 24 sleep(rand()%3); 25 } 26 } 27 28 int main() 29 { 30 pthread_t thr[2]; 31 pthread_create(&thr[0], NULL, thr1, NULL); 32 pthread_create(&thr[1], NULL, thr2, NULL); 33 34 pthread_join(thr[0], NULL); 35 pthread_join(thr[1], NULL); 36 37 return 0; 38 }
練習:修改該程序,使用mutex互斥鎖進行同步。
定義全局互斥量,初始化init(&m, NULL)互斥量,添加對應的destry
兩個線程while中,兩次printf先後,分別加lock和unlock
將unlock挪至第二個sleep後,發現交替現象很難出現。
線程在操做完共享資源後本應該當即解鎖,但修改後,線程抱着鎖睡眠。睡醒解鎖後又當即加鎖,這兩個庫函數自己不會阻塞。
因此在這兩行代碼之間失去cpu的機率很小。所以,另一個線程很可貴到加鎖的機會。
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <pthread.h> 4 #include <stdlib.h> 5 6 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 7 8 void *thr1(void *arg) 9 { 10 while(1) 11 { 12 //先上鎖 13 pthread_mutex_lock(&mutex); //加鎖當有線程已經枷鎖的時候,阻塞 14 printf("hello"); 15 sleep(rand()%3); 16 printf("world\n"); 17 //sleep(rand()%3); 18 //解鎖 19 pthread_mutex_unlock(&mutex); 20 sleep(rand()%3); 21 } 22 } 23 24 void *thr2(void *arg) 25 { 26 while(1) 27 { 28 //上鎖 29 pthread_mutex_lock(&mutex); 30 printf("HELLO"); 31 sleep(rand()%3); 32 printf("WORLD\n"); 33 //sleep(rand()%3); 34 //解鎖 35 pthread_mutex_unlock(&mutex); 36 sleep(rand()%3); 37 } 38 } 39 40 int main() 41 { 42 pthread_t thr[2]; 43 pthread_create(&thr[0], NULL, thr1, NULL); 44 pthread_create(&thr[1], NULL, thr2, NULL); 45 46 pthread_join(thr[0], NULL); 47 pthread_join(thr[1], NULL); 48 49 return 0; 50 }
結論:
在訪問共享資源前加鎖,訪問結束後當即解鎖。鎖的「粒度」應越小越好。
4)死鎖
練習:編寫程序,實現上述兩種死鎖現象。
(2)讀寫鎖
1)概念
與互斥量相似,但讀寫鎖容許更高的並行性。其特性爲:寫獨佔,讀共享。
2)讀寫鎖狀態
一把讀寫鎖具有三種狀態:
a. 讀模式下加鎖狀態 (讀鎖)
b. 寫模式下加鎖狀態 (寫鎖)
c. 不加鎖狀態
3)讀寫鎖特性
讀寫鎖也叫共享-獨佔鎖。當讀寫鎖以讀模式鎖住時,它是以共享模式鎖住的;當它以寫模式鎖住時,它是以獨佔模式鎖住的。寫獨佔、讀共享。
讀寫鎖很是適合於對數據結構讀的次數遠大於寫的狀況。
3)主要函數
pthread_rwlock_init函數 pthread_rwlock_destroy函數 pthread_rwlock_rdlock函數 pthread_rwlock_wrlock函數 pthread_rwlock_tryrdlock函數 pthread_rwlock_trywrlock函數 pthread_rwlock_unlock函數 以上7 個函數的返回值都是:成功返回0, 失敗直接返回錯誤號。 pthread_rwlock_t類型 用於定義一個讀寫鎖變量。 pthread_rwlock_t rwlock;
初始化一把讀寫鎖
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
參2:attr表讀寫鎖屬性,一般使用默認屬性,傳NULL便可。
銷燬一把讀寫鎖
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
以讀方式請求讀寫鎖。(常簡稱爲:請求讀鎖)
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
以寫方式請求讀寫鎖。(常簡稱爲:請求寫鎖)
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
解鎖
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
非阻塞以讀方式請求讀寫鎖(非阻塞請求讀鎖)
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
非阻塞以寫方式請求讀寫鎖(非阻塞請求寫鎖)
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
4)讀寫鎖示例
看以下示例,同時有多個線程對同一全局數據讀、寫操做。
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <pthread.h> 4 5 int begin_num = 1000; 6 pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER; 7 8 void *thr_write(void *arg) 9 { 10 while(1) 11 { 12 pthread_rwlock_wrlock(&rwlock); 13 printf("funcname = %s, self = %lu, begin_num = %d\n", __FUNCTION__, pthread_self(), begin_num++); 14 usleep(2000); //模擬佔用時間 15 pthread_rwlock_unlock(&rwlock); 16 usleep(3000); //防止釋放鎖以後又去搶 17 } 18 } 19 20 void *thr_read(void *arg) 21 { 22 while(1) 23 { 24 pthread_rwlock_wrlock(&rwlock); 25 printf("funcname = %s, self = %lu, begin_num = %d\n", __FUNCTION__, pthread_self(), begin_num); 26 usleep(2000); //模擬佔用時間 27 pthread_rwlock_unlock(&rwlock); 28 usleep(2000); //防止釋放鎖以後又去搶 29 } 30 } 31 int main() 32 { 33 int n = 8, i = 0; 34 pthread_t tid[8]; //5-read, 3-write 35 for (i = 0; i < 5; i++) 36 { 37 pthread_create(&tid[i], NULL, thr_read, NULL); 38 } 39 40 for (; i < n; i++) 41 { 42 pthread_create(&tid[i], NULL, thr_write, NULL); 43 } 44 45 for (i = 0; i < n; i++) 46 { 47 pthread_join(tid[i], NULL); 48 } 49 50 return 0; 51 }
讀寫鎖場景練習:
a. 線程A加寫鎖成功,線程B請求讀鎖
線程B阻塞
b. 線程A持有讀鎖,線程B請求寫鎖
線程B阻塞
c. 線程A持有讀鎖,線程B請求讀鎖
B加鎖成功
d. 線程A持有讀鎖,而後線程B請求寫鎖,而後線程C請求讀鎖
BC阻塞
A釋放後,B加鎖
B釋放後,C加鎖
e. 線程A持有寫鎖,而後線程B請求讀鎖,而後線程C請求寫鎖
BC阻塞
A釋放,C加鎖
C釋放,B加鎖
(3)條件變量
1)概念
條件變量自己不是鎖!但它也能夠形成線程阻塞。一般與互斥鎖配合使用。給多線程提供一個會合的場所。
2)主要應用函數
pthread_cond_init函數 pthread_cond_destroy函數 pthread_cond_wait函數 pthread_cond_timedwait函數 pthread_cond_signal函數 pthread_cond_broadcast函數 以上6 個函數的返回值都是:成功返回0, 失敗直接返回錯誤號。 pthread_cond_t類型 用於定義條件變量 pthread_cond_t cond;
初始化一個條件變量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
參2:attr表條件變量屬性,一般爲默認值,傳NULL便可
也可使用靜態初始化的方法,初始化條件變量:
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
銷燬一個條件變量
int pthread_cond_destroy(pthread_cond_t *cond);
阻塞等待一個條件變量
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
函數做用:
a. 阻塞等待條件變量cond(參1)知足
b. 釋放已掌握的互斥鎖(解鎖互斥量)至關於pthread_mutex_unlock(&mutex);
a.b.兩步爲一個原子操做。
c. 當被喚醒,pthread_cond_wait函數返回時,解除阻塞並從新申請獲取互斥鎖pthread_mutex_lock(&mutex);
限時等待一個條件變量
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
參3: 參看man sem_timedwait函數,查看struct timespec結構體。
struct timespec { time_t tv_sec; /* seconds */ 秒 long tv_nsec; /* nanosecondes*/ 納秒 }
形參abstime:絕對時間。
如:time(NULL)返回的就是絕對時間。而alarm(1)是相對時間,相對當前時間定時1秒鐘。
struct timespec t = {1, 0};
pthread_cond_timedwait (&cond, &mutex, &t); 只能定時到 1970年1月1日 00:00:01秒(早已通過去)
正確用法:
time_t cur = time(NULL); 獲取當前時間。 struct timespec t; 定義timespec 結構體變量t t.tv_sec = cur+1; 定時1秒 pthread_cond_timedwait (&cond, &mutex, &t); 傳參 參APUE.11.6線程同步條件變量小節 在講解setitimer函數時咱們還提到另一種時間類型: struct timeval { time_t tv_sec; /* seconds */ 秒 suseconds_t tv_usec; /* microseconds */ 微秒 };
喚醒至少一個阻塞在條件變量上的線程
int pthread_cond_signal(pthread_cond_t *cond);
喚醒所有阻塞在條件變量上的線程
int pthread_cond_broadcast(pthread_cond_t *cond);
3)生產者消費者條件變量模型
線程同步典型的案例即爲生產者消費者模型,而藉助條件變量來實現這一模型,是比較常見的一種方法。假定有兩個線程,一個模擬生產者行爲,一個模擬消費者行爲。兩個線程同時操做一個共
享資源(通常稱之爲匯聚),生產向其中添加產品,消費者從中消費掉產品。
看以下示例,使用條件變量模擬生產者、消費者問題:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <pthread.h> 4 #include <stdlib.h> 5 6 int begin_num = 1000; 7 8 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 9 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; 10 11 typedef struct _ProdInfo{ 12 int num; 13 struct _ProdInfo *next; 14 }ProdInfo; 15 16 ProdInfo *Head = NULL; 17 18 void *thr_producter(void *arg) 19 { 20 //負責在鏈表添加數據 21 while(1) 22 { 23 ProdInfo *prod = malloc(sizeof(ProdInfo)); 24 prod->num = begin_num++; 25 printf("funcname = %s, self = %lu, data = %d\n", __FUNCTION__, pthread_self(), prod->num); 26 27 pthread_mutex_lock(&mutex); 28 //add to list 29 prod->next = Head; 30 Head = prod; 31 pthread_mutex_unlock(&mutex); 32 33 //發起通知 34 pthread_cond_signal(&cond); 35 sleep(rand()%3); 36 } 37 38 return NULL; 39 } 40 41 void *thr_consumer(void *arg) 42 { 43 ProdInfo *prod = NULL; 44 45 while(1) 46 { 47 //取鏈表的數據 48 pthread_mutex_lock(&mutex); 49 //if (Head == NULL) 50 while (Head == NULL) 51 { 52 pthread_cond_wait(&cond, &mutex); //在此以前必須先加鎖 53 } 54 prod = Head; 55 Head = Head->next; 56 printf("funcname = %s, self = %lu, data = %d\n", __FUNCTION__, pthread_self(), prod->num); 57 pthread_mutex_unlock(&mutex); 58 sleep(rand()%3); 59 free(prod); 60 } 61 62 return NULL; 63 } 64 65 int main() 66 { 67 pthread_t tid[3]; 68 pthread_create(&tid[0], NULL, thr_producter, NULL); 69 pthread_create(&tid[1], NULL, thr_consumer, NULL); 70 pthread_create(&tid[2], NULL, thr_consumer, NULL); 71 72 pthread_join(tid[0], NULL); 73 pthread_join(tid[1], NULL); 74 pthread_join(tid[2], NULL); 75 76 pthread_mutex_destroy(&mutex); 77 pthread_cond_destroy(&cond); 78 79 return 0; 80 }
4)條件變量的優勢
相較於mutex而言,條件變量能夠減小競爭。
如直接使用mutex,除了生產者、消費者之間要競爭互斥量之外,消費者之間也須要競爭互斥量,但若是匯聚(鏈表)中沒有數據,消費者之間競爭互斥鎖是無心義的。有了條件變量機制之後,只有
生產者完成生產,纔會引發消費者之間的競爭。提升了程序效率。
(4)信號量
1)概念
進化版的互斥鎖(1 --> N)
因爲互斥鎖的粒度比較大,若是咱們但願在多個線程間對某一對象的部分數據進行共享,使用互斥鎖是沒有辦法實現的,只能將整個數據對象鎖住。這樣雖然達到了多線程操做共享數據時保證數
據正確性的目的,卻無形中致使線程的併發性降低。線程從並行執行,變成了串行執行。與直接使用單進程無異。
信號量,是相對摺中的一種處理方式,既能保證同步,數據不混亂,又能提升線程併發。
2)主要應用函數
sem_init函數 sem_destroy函數 sem_wait函數 sem_trywait函數 sem_timedwait函數 sem_post函數 以上6 個函數的返回值都是:成功返回0, 失敗返回-1,同時設置errno。(注意,它們沒有pthread前綴) sem_t類型,本質還是結構體。但應用期間可簡單看做爲整數,忽略實現細節(相似於使用文件描述符)。 sem_t sem; 規定信號量sem不能 < 0。頭文件 <semaphore.h>
信號量基本操做:
sem_wait:
a. 信號量大於0,則信號量-- (類比pthread_mutex_lock)
b. 信號量等於0,形成線程阻塞
對應 ->
sem_post: 將信號量++,同時喚醒阻塞在信號量上的線程 (類比pthread_mutex_unlock)
但因爲sem_t的實現對用戶隱藏,因此所謂的++、--操做只能經過函數來實現,而不能直接++、--符號。
信號量的初值,決定了佔用信號量的線程的個數。
初始化一個信號量
int sem_init(sem_t *sem, int pshared, unsigned int value);
參1:sem信號量
參2:pshared取0用於線程間;取非0(通常爲1)用於進程間
參3:value指定信號量初值
銷燬一個信號量
int sem_destroy(sem_t *sem);
給信號量加鎖 --
int sem_wait(sem_t *sem);
給信號量解鎖 ++
int sem_post(sem_t *sem);
嘗試對信號量加鎖 -- (與sem_wait的區別類比lock和trylock)
int sem_trywait(sem_t *sem);
限時嘗試對信號量加鎖 --
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
參2:abs_timeout採用的是絕對時間。
定時1秒: time_t cur = time(NULL); 獲取當前時間。 struct timespec t; 定義timespec 結構體變量t t.tv_sec = cur+1; 定時1秒 t.tv_nsec = t.tv_sec +100; sem_timedwait(&sem, &t); 傳參
3)生產者消費者信號量模型
使用信號量完成線程間同步,模擬生產者,消費者問題。
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <pthread.h> 4 #include <semaphore.h> 5 #include <stdlib.h> 6 7 //blank -- 生產者 8 //xfull -- 消費者 9 sem_t blank, xfull; 10 #define _SEM_CNT_ 5 11 12 int queue[_SEM_CNT_]; //存放生產者數據 13 int begin_num = 100; 14 15 void *thr_producter(void *arg) 16 { 17 int i = 0; 18 while(1) 19 { 20 sem_wait(&blank); //申請資源 blank-- 21 printf("funcname = %s, self = %lu, num = %d\n", __FUNCTION__, pthread_self(), begin_num); 22 queue[(i++)%_SEM_CNT_] = begin_num++; 23 sem_post(&xfull); //xfull++ 24 sleep(rand()%3); 25 } 26 27 return NULL; 28 } 29 void *thr_consumer(void *arg) 30 { 31 int i = 0; 32 int num = 0; 33 while(1) 34 { 35 sem_wait(&xfull); 36 num = queue[(i++)%_SEM_CNT_]; 37 printf("funcname = %s, self = %lu, num = %d\n", __FUNCTION__, pthread_self(), num); 38 sem_post(&blank); 39 sleep(rand()%3); 40 } 41 42 return NULL; 43 } 44 45 int main() 46 { 47 sem_init(&blank, 0, _SEM_CNT_); 48 sem_init(&xfull, 0, 0); //消費者一開始初始化,默認沒有產品 49 50 pthread_t tid[2]; 51 pthread_create(&tid[0], NULL, thr_producter, NULL); 52 pthread_create(&tid[1], NULL, thr_consumer, NULL); 53 54 pthread_join(tid[0], NULL); 55 pthread_join(tid[1], NULL); 56 57 sem_destroy(&blank); 58 sem_destroy(&xfull); 59 60 return 0; 61 }
(5)進程間同步
互斥量mutex
進程間也可使用互斥鎖,來達到同步的目的。但應在pthread_mutex_init初始化以前,修改其屬性爲進程間共享。mutex的屬性修改函數主要有如下幾個。
主要應用函數:
pthread_mutexattr_t mattr 類型: 用於定義mutex鎖的【屬性】 pthread_mutexattr_init函數: 初始化一個mutex屬性對象 int pthread_mutexattr_init(pthread_mutexattr_t *attr); pthread_mutexattr_destroy函數: 銷燬mutex屬性對象 (而非銷燬鎖) int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); pthread_mutexattr_setpshared函數: 修改mutex屬性。 int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared); 參2:pshared取值: 線程鎖:PTHREAD_PROCESS_PRIVATE (mutex的默認屬性即爲線程鎖,進程間私有) 進程鎖:PTHREAD_PROCESS_SHARED
進程間mutex示例:
1 #include <fcntl.h> 2 #include <pthread.h> 3 #include <sys/mman.h> 4 #include <sys/wait.h> 5 6 struct mt{ 7 int num; 8 pthread_mutex_t mutex; 9 pthread_mutexattr_t mutexattr; 10 }; 11 12 int main(void) 13 { 14 int fd, i; 15 struct mt *mm; 16 pid_t pid; 17 18 fd = open("mt_test", O_CREAT | O_RDWR, 0777); 19 ftruncate(fd, sizeof(*mm)); 20 mm = mmap(NULL, sizeof(*mm), PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0); 21 close(fd); 22 unlink("mt_test"); 23 //mm = mmap(NULL, sizeof(*mm), PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANON, -1, 0); 24 memset(mm, 0, sizeof(*mm)); 25 pthread_mutexattr_init(&mm->mutexattr); //初始化mutex屬性對象 26 pthread_mutexattr_setpshared(&mm->mutexattr, PTHREAD_PROCESS_SHARED); //修改屬性爲進程間共享 27 pthread_mutex_init(&mm->mutex, &mm->mutexattr); //初始化一把mutex瑣 28 pid = fork(); 29 if (pid == 0) { 30 for (i = 0; i < 10; i++) { 31 pthread_mutex_lock(&mm->mutex); 32 (mm->num)++; 33 printf("-child----num++ %d\n", mm->num); 34 pthread_mutex_unlock(&mm->mutex); 35 sleep(1); 36 } 37 } else if (pid > 0) { 38 for ( i = 0; i < 10; i++) { 39 sleep(1); 40 pthread_mutex_lock(&mm->mutex); 41 mm->num += 2; 42 printf("-parent---num+=2 %d\n", mm->num); 43 pthread_mutex_unlock(&mm->mutex); 44 } 45 wait(NULL); 46 } 47 48 pthread_mutexattr_destroy(&mm->mutexattr); //銷燬mutex屬性對象 49 pthread_mutex_destroy(&mm->mutex); //銷燬mutex 50 munmap(mm,sizeof(*mm)); //釋放映射區 51 52 return 0; 53 }
(6)文件鎖
藉助 fcntl函數來實現鎖機制。 操做文件的進程沒有得到鎖時,能夠打開,但沒法執行read、write操做。
fcntl函數: 獲取、設置文件訪問控制屬性。
int fcntl(int fd, int cmd, ... /* arg */ );
參2:
F_SETLK (struct flock *) 設置文件鎖(trylock) F_SETLKW (struct flock *) 設置文件鎖(lock)W --> wait F_GETLK (struct flock *) 獲取文件鎖
參3:
struct flock { ... short l_type; 鎖的類型:F_RDLCK 、F_WRLCK 、F_UNLCK short l_whence; 偏移位置:SEEK_SET、SEEK_CUR、SEEK_END off_t l_start; 起始偏移:1000 off_t l_len; 長度:0表示整個文件加鎖 pid_t l_pid; 持有該鎖的進程ID:(F_GETLK only) ... };
進程間文件鎖示例:
1 #include <stdio.h> 2 #include <unistd.h> 3 #include <sys/types.h> 4 #include <sys/stat.h> 5 #include <fcntl.h> 6 #include <stdlib.h> 7 8 #define __FILE_NAME__ "/opt/linuxC/09-linux-day09/temp.lock" 9 10 int main() 11 { 12 int fd = open(__FILE_NAME__, O_RDWR|O_CREAT, 0666); 13 if (fd < 0) 14 { 15 perror("open err:"); 16 return -1; 17 } 18 19 struct flock lk; 20 lk.l_type = F_WRLCK; 21 lk.l_whence = SEEK_SET; 22 lk.l_start = 0; 23 lk.l_len = 0; 24 25 if (fcntl(fd, F_SETLK, &lk) < 0) 26 { 27 perror("get lock err:"); 28 exit(1); 29 } 30 //核心邏輯 31 while(1) 32 { 33 printf("I am alive!\n"); 34 sleep(1); 35 } 36 return 0; 37 }
依然遵循「讀共享、寫獨佔」特性。但!如若進程不加鎖直接操做文件,依然可訪問成功,但數據勢必會出現混亂。
【思考】:多線程中,可使用文件鎖嗎?
多線程間共享文件描述符,而給文件加鎖,是經過修改文件描述符所指向的文件結構體中的成員變量來實現的。所以,多線程中沒法使用文件鎖。