Linux C編程之十五 線程同步


一、整體大綱

二、線程同步

1. 同步概念

    所謂同步,即同時起步,協調一致。不同的對象,對“同步”的理解方式略有不同。如,設備同步,是指在兩個設備之間規定一個共同的時間參考;數據庫同步,是指讓兩個或多個數據庫內容保持一

致,或者按需要部分保持一致;文件同步,是指讓兩個或多個文件夾里的文件保持一致等等。

    而編程中、通信中所說的同步與生活中大家印象中的同步概念略有差異。“同”字應是指協同、協助、互相配合。主旨在協同步調,按預定的先后次序運行。

2. 線程同步

(1)線程同步概念

    同步即協同步調,按預定的先后次序運行。

    線程同步,指一個線程發出某一功能調用時,在沒有得到結果之前,該調用不返回。同時其它線程為保證數據一致性,不能調用該功能。

    舉例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只有兩種取值1、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 }
進程間文件鎖

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

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

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM