无名信号量——线程间同步

一、 概述

  在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。

相关文章
相关标签/搜索