Linux C編程之十五 線程同步

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
  • pthread_mutex_init函數

    初始化一個互斥鎖(互斥量) ---> 初值可看做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)

  • pthread_mutex_destroy函數

    銷燬一個互斥鎖

int pthread_mutex_destroy(pthread_mutex_t *mutex);
  • pthread_mutex_lock函數

    加鎖。可理解爲將 mutex--(或-1)

int pthread_mutex_lock(pthread_mutex_t *mutex);
  • pthread_mutex_unlock函數

    解鎖。可理解爲將mutex ++(或+1)

int pthread_mutex_unlock(pthread_mutex_t *mutex);
  • pthread_mutex_trylock函數

    嘗試加鎖

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 }
trylock示例

    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 }
bug修復版(加互斥鎖)

    結論:

    在訪問共享資源前加鎖,訪問結束後當即解鎖。鎖的「粒度」應越小越好。

    4)死鎖

  • 線程試圖對同一個互斥量A加鎖兩次。
  • 線程1擁有A鎖,請求得到B鎖;線程2擁有B鎖,請求得到A鎖

    練習:編寫程序,實現上述兩種死鎖現象。

(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;
  • pthread_rwlock_init函數

    初始化一把讀寫鎖

int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

    參2:attr表讀寫鎖屬性,一般使用默認屬性,傳NULL便可。

  • pthread_rwlock_destroy函數

    銷燬一把讀寫鎖

int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
  • pthread_rwlock_rdlock函數

    以讀方式請求讀寫鎖。(常簡稱爲:請求讀鎖)

int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
  • pthread_rwlock_wrlock函數

    以寫方式請求讀寫鎖。(常簡稱爲:請求寫鎖)

int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
  • pthread_rwlock_unlock函數

    解鎖

int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
  • pthread_rwlock_tryrdlock函數

    非阻塞以讀方式請求讀寫鎖(非阻塞請求讀鎖)

int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
  • pthread_rwlock_trywrlock函數

    非阻塞以寫方式請求讀寫鎖(非阻塞請求寫鎖)

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;
  • pthread_cond_init函數

    初始化一個條件變量

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;
  • pthread_cond_destroy函數

    銷燬一個條件變量

int pthread_cond_destroy(pthread_cond_t *cond);
  • pthread_cond_wait函數

    阻塞等待一個條件變量

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);

  • pthread_cond_timedwait函數

    限時等待一個條件變量

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 */ 微秒 };
  • pthread_cond_signal函數

    喚醒至少一個阻塞在條件變量上的線程

int pthread_cond_signal(pthread_cond_t *cond);
  • pthread_cond_broadcast函數

    喚醒所有阻塞在條件變量上的線程

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的實現對用戶隱藏,因此所謂的++、--操做只能經過函數來實現,而不能直接++、--符號。

    信號量的初值,決定了佔用信號量的線程的個數。

  • sem_init函數

    初始化一個信號量

int sem_init(sem_t *sem, int pshared, unsigned int value);

    參1:sem信號量

    參2:pshared取0用於線程間;取非0(通常爲1)用於進程間

    參3:value指定信號量初值

  • sem_destroy函數

    銷燬一個信號量

int sem_destroy(sem_t *sem);
  • sem_wait函數

    給信號量加鎖 --

int sem_wait(sem_t *sem);
  • sem_post函數

    給信號量解鎖 ++

int sem_post(sem_t *sem);
  • sem_trywait函數

    嘗試對信號量加鎖 -- (與sem_wait的區別類比lock和trylock)

int sem_trywait(sem_t *sem);
  • sem_timedwait函數

    限時嘗試對信號量加鎖 --

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 }
進程間使用mutex來實現通訊

(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 }
進程間文件鎖

    依然遵循「讀共享、寫獨佔」特性。但!如若進程不加鎖直接操做文件,依然可訪問成功,但數據勢必會出現混亂。

  【思考】:多線程中,可使用文件鎖嗎?

    多線程間共享文件描述符,而給文件加鎖,是經過修改文件描述符所指向的文件結構體中的成員變量來實現的。所以,多線程中沒法使用文件鎖。

相關文章
相關標籤/搜索