無名信號量——線程間同步

一、 概述

  在linux中,線程就至關於一個輕量級的進程,它經常被用來完成某種特定功能的事情。假如一個進程建立了多個線程,這些線程要一塊兒配合完成一件更大的事情,這個時候就須要用到線程同步機制了。在Linux中一般用信號量實現線程間的同步。linux

  這種情形能夠用現實生活中來舉例子,好比甲乙兩我的用雙人手拉鋸鋸木頭,甲拉一下而後乙拉一下,必須這樣才能配合把木頭鋸斷。在這個情形之下,甲拉完一下以後必須等乙拉完才能再次拉,乙也是如此,它們之間的信號量值最大爲1。函數

  又或者甲乙丙三我的種樹,甲負責挖洞,乙負責放樹苗,丙負責填洞,那流程是「挖洞->放樹苗->填洞」, 「挖洞->放樹苗->填洞」...。甲的工做不用受乙和丙的影響,他能夠在乙來不及放樹苗的狀況下挖不少洞,而乙放樹苗只要等甲挖好洞了就能夠放,他不用管丙填了多少個洞,若是乙將全部的洞都放好了樹苗,那麼乙就必須等待甲挖好下一個洞才能放樹苗,一樣的若是丙將全部放了樹苗的坑都填了,那麼他必須等乙放好下一棵樹苗才能填洞,而不關心甲挖了多少個洞。這種狀況下甲每挖一個洞,甲和乙之間的信號量的值加一,乙每放一棵樹苗,甲和乙之間的信號量的值減一,若是信號量的值爲0,那麼乙就得等。乙和丙也是如此,可是「甲—乙」和「乙—丙」之間用的信號量不是同一個。post

  信號量分爲有名信號量和無名信號量,實際上線程間同步用的是無名信號量,進程間同步才用有名信號量(也能夠用無名信號量進行進程間同步)。測試

  使用信號量須要包含頭文件semaphore.h,且編譯時須要連接庫-lrt或者-lpthread。spa

二、函數介紹

 

2.1 初始化信號量

2.1.1 sem_init

  函數原型:int sem_init(sem_t *sem, int pshared, unsigned int value);線程

  功能:初始化信號量code

  參數[out]:sem:初始化的信號量。orm

  參數[in]:pshared:信號量共享的範圍。blog

        0:線程間使用。進程

        非0:進程間使用。

  參數[in]:value:信號量初值。

  返回:成功返回0,失敗返回-1。

2.2 獲取信號量

2.2.1 sem_wait

  函數原型:int sem_wait(sem_t *sem);

  功能:等待信號量。若是信號量的值大於0,那麼該函數當即返回,信號量的值減1,若是信號量的值等於0,那麼阻塞等待直到信號量的的值大於1,而後信號量的值減1。

  參數[in]:sem:從sem_init函數獲得的信號量。

  返回:成功返回0,失敗返回-1。

 

2.2.2 sem_trywait

  函數原型:int sem_trywait(sem_t *sem);

  功能:嘗試等待信號量。該函數會當即返回,若是信號量的值大於0,那麼信號量的值減1,函數返回0,不然函數返回-1,而且將errno置爲EAGAIN,其值爲11,表示Try again,形成錯誤的緣由是「資源暫時不可用」。

  參數[in]:sem:從sem_init函數獲得的信號量。

  返回:成功返回0,失敗返回-1。

 

2.2.3 sem_timedwait

  函數原型:int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

  功能:在傳入的時間內阻塞等待信號量。

  參數[in]:sem:從sem_init函數獲得的信號量。

  參數[in]:abs_timeout:阻塞等待的系統實時時間的時間點。struct timespec結構定義以下:

    struct timespec
    {
            time_t tv_sec; /* 秒*/
            long tv_nsec; /* 納秒*/
    };

    特別注意該結構表示的不是延時等待的時間,而是系統的實時時間。我開始覺得好比等待3秒,那麼將tv_sec設置爲3,tv_nsec設置爲0便可,這樣帶來的結果是函數馬上返回了。傳入的值其實是某個時刻,能夠認爲它等到這個時刻若是尚未信號量被釋放他就不等了。想要肯定此時的時刻是多少,能夠經過clock_gettime函數獲取,該函數的函數原型爲:int clock_gettime(clockid_t clk_id, struct timespec* tp);

    調用clock_gettime函數需包含time.h,編譯時須要連接-lrt

    clk_id有下列幾種選擇:

    CLOCK_REALTIME:系統實時時間,隨系統實時時間改變而改變,即從UTC1970-1-1 0:0:0開始計時,中間時刻若是系統時間被用戶改爲其餘,則對應的時間相應改變
    CLOCK_MONOTONIC:從系統啓動這一刻起開始計時,不受系統時間被用戶改變的影響
    CLOCK_PROCESS_CPUTIME_ID:本進程到當前代碼系統CPU花費的時間
    CLOCK_THREAD_CPUTIME_ID:本線程到當前代碼系統CPU花費的時間

    因爲sem_timedwait函數等待的是系統實時時間,所以超時等待三秒的代碼能夠這樣寫:

clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 3;
ret = sem_timedwait(&sem, &ts);

    在調用sem_timedwait時,若是有信號量,那麼函數當即返回0。

    若是沒有信號量釋放,而且系統時間已通過了指定的時間,那麼函數當即返回-1,而且將errno置爲ETIMEDOUT,其值爲110,表示超時。

    若是沒有信號量釋放,而且系統時間還沒過指定的時間,若是在時間到以前有信號量釋放,函數返回0,若是時間到了尚未信號量釋放,那麼函數返回-1,而且置errno爲ETIMEDOUT。

    這裏有一個相似於bug的東西,因爲sem_timedwait函數是等待系統時間到達某個點,若是在等待期間系統時間被改變了,等待的時間點卻不會變。這就像你和女友約好了晚上8點見面,實際上指的是你手錶上的晚上8點,你只要保證你的手錶上的時間不會達到8點,那麼就不會超時。

  返回:成功返回0,失敗返回-1。

2.3 釋放信號量

2.3.1 sem_post

  函數原型:int sem_post(sem_t *sem);

  功能:釋放信號量,每調用一次sem_post,信號量的值加1。

  參數:sem:信號量

  返回:成功返回0,失敗返回-1。失敗的狀況有2種,第一是傳入的信號量無效,那麼errno被置爲EINVAL,第二種是信號量的值將要超過可達到的最大值,那麼errno被設置爲EOVERFLOW。這個最大值就是int類型的最大值2147483647(2^31 - 1)。

2.4 獲取信號量的值

2.4.1 sem_getvalue

  函數原型:int sem_getvalue(sem_t *sem, int *sval);

  功能:獲取信號量的值。

  參數[in]:sem:信號量

  參數[out]:sval:用於保存信號量的值

  返回:成功返回0,失敗返回-1。

2.5 銷燬信號量

2.5.1 int sem_destroy

  函數原型:int sem_destroy(sem_t *sem);

  功能:銷燬信號量。

  參數[in]:sem:信號量

  返回:成功返回0,失敗返回-1。

三、 測試程序

3.1 測試程序1

 1 /**
 2   * filename: sem.c
 3   * author: Suzkfly
 4   * date: 2021-01-27
 5   * platform: Ubuntu
 6   *     該例程測試了sem_wait的用法
 7   *     編譯時加-lpthread
 8   */
 9 #include <stdio.h>
10 #include <pthread.h>
11 #include <semaphore.h>
12 
13 sem_t g_sem;    /* 定義信號量 */
14 
15 void *pthread_func1(void *p_arg)
16 {
17     int value = 0;
18     
19     sem_getvalue(&g_sem, &value);           /* 獲取信號量的值 */
20     printf("value = %d\n", value);
21     printf("%s sem_wait...\n", __func__);
22     sem_wait(&g_sem);
23     printf("%s sem_wait succeed\n", __func__);
24 }
25 
26 void *pthread_func2(void *p_arg)
27 {
28     int value = 0;
29 
30     sleep(1);   /* 讓pthread_func1先獲取到信號量 */
31     sem_getvalue(&g_sem, &value);
32     printf("value = %d\n", value);
33     printf("%s sem_wait...\n", __func__);
34     sem_wait(&g_sem);
35     printf("%s sem_wait succeed\n", __func__);  /* 這句打印不出來 */
36 }
37 
38 int main(int argc, const char *argv[])
39 {
40     pthread_t pthread;
41     pthread_t pthread2;
42     int ret;
43     
44     ret = sem_init(&g_sem, 0, 1);       /* 初始化信號量值爲1 */
45     if (ret == -1) {
46         printf("sem_init failed\n");
47         return 0;
48     }
49     
50     pthread_create(&pthread,  NULL, pthread_func1, NULL);    /* 建立線程 */
51     pthread_create(&pthread2, NULL, pthread_func2, NULL);    /* 建立線程 */
52     
53     pthread_join(pthread,  NULL);    /* 阻塞等待回收線程資源 */
54     pthread_join(pthread2, NULL);    /* 阻塞等待回收線程資源 */
55     
56     return 0;
57 }

測試結果:

 

 

 代碼分析:

  第44行將信號量的值初始化爲1,代表在沒有調用sem_post的狀況下只能有一次sem_wait成功,在第30行讓線程2睡眠1秒,目的是確保線程1先獲得信號量,線程1在sem_wait以前先獲取信號量的值,其值爲1,那麼在sem_wait以後信號量的值變爲0,那麼線程2調用sem_wait將一直阻塞,不會執行後面的語句。

3.2 測試程序2

 1 /**
 2   * filename: sem.c
 3   * author: Suzkfly
 4   * date: 2021-01-27
 5   * platform: Ubuntu
 6   *     該例程測試了sem_wait的用法,實現拉鋸效果
 7   *     編譯時加-lpthread
 8   */
 9 #include <stdio.h>
10 #include <pthread.h>
11 #include <semaphore.h>
12 
13 sem_t g_sem[2];    /* 定義信號量 */
14 
15 void *pthread_func1(void *p_arg)
16 {
17     while (1) {
18         sem_wait(&g_sem[0]);
19         printf("1\n");
20         sem_post(&g_sem[1]);
21     }
22 }
23 
24 void *pthread_func2(void *p_arg)
25 {
26     while (1) {
27         sem_wait(&g_sem[1]);
28         printf("2\n");
29         sem_post(&g_sem[0]);
30     }
31 }
32 
33 int main(int argc, const char *argv[])
34 {
35     pthread_t pthread;
36     pthread_t pthread2;
37     
38     sem_init(&g_sem[0], 0, 1);       /* 初始化信號量值爲0 */
39     sem_init(&g_sem[1], 0, 0);       /* 初始化信號量值爲0 */
40 
41     pthread_create(&pthread,  NULL, pthread_func1, NULL);    /* 建立線程 */
42     pthread_create(&pthread2, NULL, pthread_func2, NULL);    /* 建立線程 */
43     
44     pthread_join(pthread,  NULL);    /* 阻塞等待回收線程資源 */
45     pthread_join(pthread2, NULL);    /* 阻塞等待回收線程資源 */
46     
47     return 0;
48 }

測試結果:

代碼分析:

  該程序初始化了2個信號量,但給g_sem[0]的值爲1,給g_sem[1]的值爲0,在兩個線程中互相給對方釋放信號量,這樣就能不斷的打印121212...,就實現了拉鋸的效果。

3.3 測試程序3

 1 /**
 2   * filename: sem.c
 3   * author: Suzkfly
 4   * date: 2021-01-27
 5   * platform: Ubuntu
 6   *     該例程測試了sem_wait的用法,實現「挖洞->放樹苗->填洞」
 7   *     編譯時加-lpthread
 8   */
 9 #include <stdio.h>
10 #include <pthread.h>
11 #include <semaphore.h>
12 
13 sem_t g_sem[2];    /* 定義信號量 */
14 
15 void *pthread_func1(void *p_arg)
16 {
17     int i = 5;
18     
19     while (i--) {
20         printf("Dig\n");    /* 挖洞 */
21         //usleep(100);      /* 若是以爲挖洞挖的太快就去掉本行註釋 */
22         sem_post(&g_sem[0]);
23     }
24 }
25 
26 void *pthread_func2(void *p_arg)
27 {
28     int i = 5;
29     
30     while (i--) {
31         sem_wait(&g_sem[0]);
32         printf("Plant\n");  /* 種樹 */
33         sem_post(&g_sem[1]);
34     }
35 }
36 
37 void *pthread_func3(void *p_arg)
38 {
39     int i = 5;
40     
41     while (i--) {
42         sem_wait(&g_sem[1]);
43         printf("Fill\n");  /* 填洞 */
44     }
45 }
46 
47 int main(int argc, const char *argv[])
48 {
49     pthread_t pthread;
50     pthread_t pthread2;
51     pthread_t pthread3;
52     
53     sem_init(&g_sem[0], 0, 0);       /* 初始化信號量值爲0 */
54     sem_init(&g_sem[1], 0, 0);       /* 初始化信號量值爲0 */
55 
56     pthread_create(&pthread,  NULL, pthread_func1, NULL);    /* 建立線程 */
57     pthread_create(&pthread2, NULL, pthread_func2, NULL);    /* 建立線程 */
58     pthread_create(&pthread3, NULL, pthread_func3, NULL);    /* 建立線程 */
59     
60     pthread_join(pthread,  NULL);    /* 阻塞等待回收線程資源 */
61     pthread_join(pthread2, NULL);    /* 阻塞等待回收線程資源 */
62     pthread_join(pthread3, NULL);    /* 阻塞等待回收線程資源 */
63     
64     return 0;
65 }

測試結果:

 

 

 代碼分析:

  本程序初始化的信號量都爲0,線程1不須要等待信號量,可是它每執行一遍就給線程2釋放一次信號量,線程2獲得信號量後給線程3釋放一次信號量,該程序的執行結果不必定如上圖那樣並排下來,它只是保證了「種樹前必須有挖好的坑,填坑前必須有種好的樹」。

3.4 測試程序4

 1 /**
 2   * filename: sem.c
 3   * author: Suzkfly
 4   * date: 2021-01-27
 5   * platform: Ubuntu
 6   *     該例程測試了sem_trywait的用法
 7   *     編譯時加-lpthread
 8   */
 9 #include <stdio.h>
10 #include <pthread.h>
11 #include <semaphore.h>
12 #include <errno.h>
13 
14 sem_t g_sem;    /* 定義信號量 */
15 
16 void *pthread_func(void *p_arg)
17 {
18     int ret = 0;
19     int value = 0;
20     
21     while (1) {
22         sem_getvalue(&g_sem, &value);
23         printf("value = %d\n", value);
24         printf("sem_trywait...\n");
25         ret = sem_trywait(&g_sem);
26         printf("ret = %d\n", ret);
27         printf("errno = %d\n", errno);
28         sleep(1);
29     }
30 }
31 
32 int main(int argc, const char *argv[])
33 {
34     pthread_t pthread;
35     
36     sem_init(&g_sem, 0, 1);       /* 初始化信號量值爲1 */
37 
38     pthread_create(&pthread,  NULL, pthread_func, NULL);    /* 建立線程 */
39     
40     pthread_join(pthread,  NULL);    /* 阻塞等待回收線程資源 */
41     
42     return 0;
43 }

測試結果:

 

 

 代碼分析:

  第36行將信號量的初值設爲1,在運行程序的時候第一次獲取信號量的值爲1,sem_trywait返回0,errno的值也爲0,以後再獲取信號量的時候value就一直爲0了,而且sem_trywait返回-1,errno的值爲11,表示「資源暫時不可用」。

3.5 測試程序5

 1 /**
 2   * filename: sem.c
 3   * author: Suzkfly
 4   * date: 2021-01-27
 5   * platform: Ubuntu
 6   *     該例程測試了sem_timedwait的用法
 7   *     編譯時加-lrt
 8   */
 9 #include <stdio.h>
10 #include <pthread.h>
11 #include <semaphore.h>
12 #include <errno.h>
13 #include <time.h>
14 
15 sem_t g_sem;    /* 定義信號量 */
16 
17 void *pthread_func(void *p_arg)
18 {
19     int ret;
20     struct timespec ts;
21     
22     clock_gettime(CLOCK_REALTIME, &ts);
23     ts.tv_sec += 5;
24     ret = sem_timedwait(&g_sem, &ts);
25     printf("ret = %d\n", ret);
26     printf("errno = %d\n", errno);
27 }
28 
29 int main(int argc, const char *argv[])
30 {
31     pthread_t pthread;
32     
33     sem_init(&g_sem, 0, 0);       /* 初始化信號量值爲0 */
34 
35     pthread_create(&pthread,  NULL, pthread_func, NULL);    /* 建立線程 */
36     
37     while (1) {
38         sleep(1);
39         printf("running...\n");
40     }
41     pthread_join(pthread,  NULL);    /* 阻塞等待回收線程資源 */
42     
43     return 0;
44 }

測試結果:

 

 

 因爲初始信號量給的是0,因此sem_timedwait函數在等待了5秒以後才返回。須要注意的是,因爲使用了clock_gettime函數,所以編譯時須要鏈接庫-lrt。

相關文章
相關標籤/搜索