Unix网络编程-同步
1、互斥鎖(量)和條件變量
默認情況下互斥鎖和條件變量用于線程間同步,若將它們放在共享內存區,也能用于進程間同步。
1.1 互斥鎖
1、概述:
互斥鎖(Mutex,也稱互斥量),防止多個線程對一個公共資源做讀寫操作的機制,以保證共享數據的完整性。
用以保護臨界區,以保證任何時候只有一個線程(或進程)在訪問共享資源(如代碼段)。保護臨界區的代碼形式:
lock_the_mutex(...); 臨界區 unlock_the_mutex(...);任何時刻只有一個線程能夠鎖住一個給定的互斥鎖。
下面的三個函數給一個互斥鎖進行上鎖和解鎖:
#include<pthread.h> int pthread_mutex_lock(pthread_mutex_t *mptr); int pthread_mutex_trylock(pthread_mutex_t *mptr); int pthread_mutex_unlock(pthread_mutex_t *mptr);以上三個函數,如果調用成功均返回0,失敗返回相應的error值。
如果嘗試給一個已由某個線程鎖住的互斥鎖上鎖,那么pthread_mutex_lock將阻塞到該互斥鎖解鎖為止。pthread_mutex_trylock是對應的非阻塞函數,如果該互斥鎖已鎖住,它就返回一個EBUSY錯誤。
如果有多個線程阻塞在等待同一個互斥鎖上,那么當該互斥鎖解鎖時,哪一個線程會開始運行:不同線程被賦予不同優先級,同步函數將喚醒優先級最高的被阻塞線程。
2、互斥鎖實現的生產者-消費者模型:
生產者-消費者問題也稱有界緩沖區問題,若干個生產者和若干個消費者共享使用固定數目的緩沖區,因而帶來的同步和通信問題。
各種IPC手段本身就是一個生產者-消費者問題的實例。
管道、FIFO和消息隊列的同步是隱式同步,使用者只能通過指定的接口來使用這些IPC方式,其中的同步都由內核完成。
共享內存作為IPC,需要使用者進行顯式同步,線程間共享全局數據也需要顯式同步。
以多生產者,單消費者的模型為例:
在單個進程中有多個生產者線程和單個消費者線程,我們只關心多個生產者線程之間的同步,直到所有生產者線程都完成工作后,才啟動消費者線程。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.h> #include <math.h>#define MAXNITEMS 1000000 #define MAXNTHREADS 100 int nitems; //(1)生產者存放的條目數,只讀(對于生產者或消費者)!!! /* *shared結構中的變量是共享數據 */ struct {//(2)!!!pthread_mutex_t mutex;/*同步變量:互斥鎖*/int buff[MAXNITEMS];//生產者會依次給buff數組存放數據int nput;/*nput是buff數組中下一次存放的元素下標*/int nval;/*nval是下一次存放的值(0,1,2等)*/ } shared = {PTHREAD_MUTEX_INITIALIZER //(3)對用于生產者線程間同步的互斥鎖做初始化!!! }; void *produce(void *), *consume(void *);int main(int argc,char *argv[]) {/**變量說明:tid_produce[]數組中保存每個線程的線程ID*count[]是每個線程計數器*tid_consume中保存單個的消費者的ID*/int i, nthreads, count[MAXNTHREADS];pthread_t tid_produce[MAXNTHREADS], tid_consume;/*命令行參數個數判斷*/if (argc != 3) {printf("usage: producer_consumer1 <#iterms> <#threads>\n");exit(1);}/** argv[1]中指定生產者存放的條目數* argv[2]中指定待創建的生產者線程的數目*/nitems = min(atoi(argv[1]), MAXNITEMS);//(4)指定生產者存放的條目數!!!nthreads = min(atoi(argv[2]), MAXNTHREADS);//(5)創建多少個生產者線程!!!/**set_concurrency函數用來告訴線程系統我們希望并發運行多少線程*即設置并發級別*/set_concurrency(nthreads);//(6)!!!//(7)創建生產者線程:每個線程執行produce!!!for (i = 0; i < nthreads; i++) {//依次將buff[i]設置為icount[i] = 0;//計數器初始化為0,每個線程每次往緩沖區存放一個條目時給這個計數器加1.pthread_create(&tid_produce[i], NULL, produce, &count[i]);}//(8)等待所有生產者線程終止,并輸出每個線程的計數器值!!!for (i = 0; i < nthreads; i++) {pthread_join(tid_produce[i], NULL);printf("count[%d] = %d\n", i, count[i]);}//(9)然后啟動單個消費者線程!!!pthread_create(&tid_consume, NULL, consume, NULL);//(10)接著等待消費者完成,然后終止進程!!!pthread_join(tid_consume, NULL);return 0; }//創建生產者線程 void *produce(void *arg) {for ( ; ; ) {pthread_mutex_lock(&shared.mutex);//(1)上鎖!!!//(2)臨界區if (shared.nput >= nitems) {//說明此時已經生產完畢,解鎖pthread_mutex_unlock(&shared.mutex);return (NULL);}shared.buff[shared.nput] = shared.nval;shared.nput++;shared.nval++;pthread_mutex_unlock(&shared.mutex);//(3)解鎖!!!//count元素的增加(通過指針arg)不屬于臨界區,因為每個線程有各自的計數器*((int *) arg) += 1;} }//等待生產者線程,然后啟動消費者線程 void *consume(void *arg) {int i;/**消費者只是驗證buff中的條目是否正確,如果發現錯誤則輸出一條信息*這個函數是只有一個實例在運行,而且是在所有的生產者線程都完成之后*因此不需要任何同步*/for (i = 0; i < nitems; i++)if (shared.buff[i] != i)printf("buff[%d] = %d\n", i, shared.buff[i]);return (NULL); }3、互斥鎖的非正常終止:
若進程在持有互斥鎖時終止,內核不會負責自動釋放持有的鎖。內核自動清理的唯一同步鎖類型是fcntl記錄鎖。
若被鎖住的互斥鎖的持有進程或線程終止,會造成這個互斥鎖無法解鎖,因而死鎖。線程可以安裝線程清理程序,用來在被取消時能釋放持有的鎖。但這種釋放可能會導致共享對象的狀態被部分更新,造成不一致。
1.2 條件變量
互斥鎖只能用于上鎖,實現對某個共享對象的互斥訪問,無法用于對某事件的等待。條件變量則用于等待。
#include<pthread.h> int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex); int int pthread_cond_signal(pthread_cond_t *cond);每個條件變量都需要關聯一個互斥鎖,用來提供對等待條件的互斥訪問。
2、讀寫鎖
讀寫鎖可以在讀數據與修改數據之間作區分。其規則如下:
1)沒有線程持有寫鎖時,任意多的線程可以持有讀鎖。
2)僅當沒有線程持有讀鎖或寫鎖時,才能分配寫鎖。
簡言之,只要沒有線程在寫,那么所有線程都可以讀;但是有線程要想寫,必須是既沒有線程在讀,也沒有線程在寫。!!!
當已有線程持有讀鎖時,另一線程申請寫鎖則會阻塞,若后續還有讀鎖的申請,此時有兩種策略:
1)對后續的讀鎖請求都通過,可能會造成因讀鎖不斷被分配,寫鎖申請始終阻塞,“餓死”了寫進程。
2)后續讀鎖請求都阻塞,等當前持有的讀鎖都結束后優先分配寫鎖。
與普通互斥鎖相比,當被保護數據的讀訪問比寫訪問更為頻繁時,讀寫鎖能提供更高的并發度。
3、記錄上鎖
記錄上鎖是讀寫鎖的一種擴展類型,它可用于有親緣關系或無親緣關系的進城之間共享某個文件的讀與寫。
執行上鎖的函數是fcntl,鎖由內核維護,其屬主由進程ID標識。
特點:只用于不同進程間的上鎖,而不是同一進程內不同線程間的上鎖。
Unix內核沒有記錄這一概念,對記錄的解釋是由讀寫文件的應用進行的。每個記錄就是文件中的一個字節范圍。
使用fcntl記錄上鎖時,等待著的讀出者優先還是等待著的寫入者優先沒有保證。
4、信號量
信號量是一種用于不同進程間,或一個給定進程內不同線程間同步手段的原語。
3中信號量類型:
1)Posix有名信號量:使用Posix IPC名字標識,可用于進程或線程間的同步。(可用于彼此無親緣關系的進程間)
2)Posix基于內存的信號量(無名信號量):存放在共享內存區,可用于進程或線程間的同步。(不可用于彼此無親緣關系的進程間)
3)System V信號量:在內核中維護,可用于進程或線程間的同步。
Posix信號量不必在內核中維護(System V信號量由內核維護),由可能為路徑名的名字來標識。
4.1 Posix信號量
1、概述
三種基本操作:
1)創建(create):指定初始值。
2)等待(wait):如果值小于等于0則阻塞,否則將其減一,又稱P操作。
3)掛出(post):將信號量的值加1,加后如果值大于0,則喚醒一個阻塞在等待上的線程,又稱V操作。
信號量的wait和post與條件變量的wait和signal類似,區別是:因為永久的改變了信號量的值,信號量的操作總被記住(會影響到后續的操作);條件變量的signal如果沒有線程在等待,該信號將丟失(對后續操作沒有影響)。
互斥鎖是為上鎖而優化的,條件變量是為等待優化的,信號量既可以上鎖也可以等待,因此開銷更大。
2、二值信號量
二值信號量,其值為0或1,資源鎖住則信號量值為0,若資源可用則信號量值為1。
二值信號量可用于互斥,就像互斥鎖一樣。但互斥鎖必須由鎖住它的線程解鎖,信號量的掛出卻不必由執行過它的等待操作的同一線程執行。
二值信號量用于生產者消費者問題:考慮往某個緩沖區放一個條目的一個生產者,以及取走該條目的一個消費者,這種簡化類型。
3、計數信號量
其值在0和某個限制值(32767內)之間,可統計資源數,信號量的值就是可用資源數。等待操作都等待信號量的值變為大于0(表示可用),然后將它減1;掛出操作則只是將信號量值加1(可用資源數增加),喚醒正在等待該信號量值變為大于0的任意線程。
4.2 System V信號量
System V信號量增加了另一級復雜度。
**計數信號量集:一個或多個信號量(構成一個集合),其中每個都是計數信號量。**System V信號量一般指計數信號量集。而Posix信號量一般指單個計數信號量。
5、信號量、互斥鎖和條件變量的差異
信號量的意圖在于進程間同步,這些進程可能共享也可能不共享內存區;互斥鎖和條件變量的意圖在于線程間同步,這些線程總是共享內存區;但是信號量也可用于線程間,互斥鎖和條件變量也可用于進程間。
1)互斥鎖總是由給他上鎖的線程解鎖,信號量的掛出也可由其他線程執行。
2)互斥鎖要么被鎖住,要么被解開(二值狀態,類似于二值信號量)。
3)信號量有一個與之關聯的狀態(計數值),信號量的掛出操作總是被記住。然而當向一個條件變量發信號時,如果沒有線程在等待,信號將丟失。
參考《Unix網絡編程:卷2》
總結
以上是生活随笔為你收集整理的Unix网络编程-同步的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Vijos——T 1092 全排列
- 下一篇: 时速云与炎黄盈动强强联手,打造企业 IT