信号量、同步这些名词在进程间通信时就已经说过,在这里它们的意思是相同的,只不过是同步的对象不同而已。但是下面介绍的信号量的接口是用于线程的信号量,注意不要跟用于进程间通信的信号量混淆,关于用于进程间通信的信号量的详细介绍可以参阅我的另一篇博文:Linux进程间通信——使用信号量。相似地,线程同步是控制线程执行和访问临界区域的方法。
 
一、什么是信号量
线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。
 
而只有0和1两种取值的信号量叫做二进制信号量,在这里将重点介绍。而信号量一般常用于保护一段代码,使其每次只被一个执行线程运行。我们可以使用二进制信号量来完成这个工作。
 
二、信号量的接口和使用
 
信号量的函数都以sem_开头,线程中使用的基本信号量函数有4个,它们都声明在头文件semaphore.h中。
 
1、sem_init函数
该函数用于创建信号量,其原型如下:

 
  1. int sem_init(sem_t *sem, int pshared, unsigned int value);
该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value为sem的初始值。调用成功时返回0,失败返回-1.
 
2、sem_wait函数
该函数用于以原子操作的方式将信号量的值减1。原子操作就是,如果两个线程企图同时给一个信号量加1或减1,它们之间不会互相干扰。它的原型如下:

 
  1. int sem_wait(sem_t *sem);
sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.
 
3、sem_post函数
该函数用于以原子操作的方式将信号量的值加1。它的原型如下:

 
  1. int sem_post(sem_t *sem);
与sem_wait一样,sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.
 
4、sem_destroy函数
该函数用于对用完的信号量的清理。它的原型如下:

 
  1. int sem_destroy(sem_t *sem);
成功时返回0,失败时返回-1.
 
三、使用信号量同步线程
 
下面以一个简单的多线程程序来说明如何使用信号量进行线程同步。在主线程中,我们创建子线程,并把数组msg作为参数传递给子线程,然后主线程等待直到有文本输入,然后调用sem_post来增加信号量的值,这样就会立刻使子线程从sem_wait的等待中返回并开始执行。线程函数在把字符串的小写字母变成大写并统计输入的字符数量之后,它再次调用sem_wait并再次被阻塞,直到主线程再次调用sem_post增加信号量的值。
 
  1. #include <unistd.h>
  2. #include <pthread.h>
  3. #include <semaphore.h>
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. #include <string.h>
  7. //线程函数
  8. void *thread_func(void *msg);
  9. sem_t sem;//信号量
  10. #define MSG_SIZE 512
  11. int main()
  12. {
  13. int res = -1;
  14. pthread_t thread;
  15. void *thread_result = NULL;
  16. char msg[MSG_SIZE];
  17. //初始化信号量,其初值为0
  18. res = sem_init(&sem, 0, 0);
  19. if(res == -1)
  20. {
  21. perror("semaphore intitialization failed\n");
  22. exit(EXIT_FAILURE);
  23. }
  24. //创建线程,并把msg作为线程函数的参数
  25. res = pthread_create(&thread, NULL, thread_func, msg);
  26. if(res != 0)
  27. {
  28. perror("pthread_create failed\n");
  29. exit(EXIT_FAILURE);
  30. }
  31. //输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n”
  32. printf("Input some text. Enter 'end'to finish...\n");
  33. while(strcmp("end\n", msg) != 0)
  34. {
  35. fgets(msg, MSG_SIZE, stdin);
  36. //把信号量加1
  37. sem_post(&sem);
  38. }
  39. printf("Waiting for thread to finish...\n");
  40. //等待子线程结束
  41. res = pthread_join(thread, &thread_result);
  42. if(res != 0)
  43. {
  44. perror("pthread_join failed\n");
  45. exit(EXIT_FAILURE);
  46. }
  47. printf("Thread joined\n");
  48. //清理信号量
  49. sem_destroy(&sem);
  50. exit(EXIT_SUCCESS);
  51. }
  52. void* thread_func(void *msg)
  53. {
  54. //把信号量减1
  55. sem_wait(&sem);
  56. char *ptr = msg;
  57. while(strcmp("end\n", msg) != 0)
  58. {
  59. int i = 0;
  60. //把小写字母变成大写
  61. for(; ptr[i] != '\0'; ++i)
  62. {
  63. if(ptr[i] >= 'a' && ptr[i] <= 'z')
  64. {
  65. ptr[i] -= 'a' - 'A';
  66. }
  67. }
  68. printf("You input %d characters\n", i-1);
  69. printf("To Uppercase: %s\n", ptr);
  70. //把信号量减1
  71. sem_wait(&sem);
  72. }
  73. //退出线程
  74. pthread_exit(NULL);
  75. }
运行结果如下:
 
 
从运行的结果来看,这个程序的确是同时在运行两个线程,一个控制输入,另一个控制处理统计和输出。
 
四、分析此信号量同步程序的缺陷
但是这个程序有一点点的小问题,就是这个程序依赖接收文本输入的时间足够长,这样子线程才有足够的时间在主线程还未准备好给它更多的单词去处理和统计之前处理和统计出工作区中字符的个数。所以当我们连续快速地给它两组不同的单词去统计时,子线程就没有足够的时间支执行,但是信号量已被增加不止一次,所以字符统计线程(子线程)就会反复处理和统计字符数目,并减少信号量的值,直到它再次变成0为止。
 
为了更加清楚地说明上面所说的情况,修改主线程的while循环中的代码,如下:
  1. printf("Input some text. Enter 'end'to finish...\n");
  2. while(strcmp("end\n", msg) != 0)
  3. {
  4. if(strncmp("TEST", msg, 4) == 0)
  5. {
  6. strcpy(msg, "copy_data\n");
  7. sem_post(&sem);
  8. }
  9. fgets(msg, MSG_SIZE, stdin);
  10. //把信号量加1
  11. sem_post(&sem);
  12. }
重新编译程序,此时运行结果如下:
 
当我们输入TEST时,主线程向子线程提供了两个输入,一个是来自键盘的输入,一个来自主线程复数据到msg中,然后从运行结果可以看出,运行出现了异常,没有处理和统计从键盘输入TEST的字符串而却对复制的数据作了两次处理。原因如上面所述。
 
五、解决此缺陷的方法
 
解决方法有两个,一个就是再增加一个信号量,让主线程等到子线程处理统计完成之后再继续执行;另一个方法就是使用互斥量。
 
下面给出用增加一个信号量的方法来解决该问题的代码,源文件名为semthread2.c,源代码如下:
  1. #include <unistd.h>
  2. #include <pthread.h>
  3. #include <semaphore.h>
  4. #include <stdlib.h>
  5. #include <stdio.h>
  6. #include <string.h>
  7. //线程函数
  8. void *thread_func(void *msg);
  9. sem_t sem;//信号量
  10. sem_t sem_add;//增加的信号量
  11. #define MSG_SIZE 512
  12. int main()
  13. {
  14. int res = -1;
  15. pthread_t thread;
  16. void *thread_result = NULL;
  17. char msg[MSG_SIZE];
  18. //初始化信号量,初始值为0
  19. res = sem_init(&sem, 0, 0);
  20. if(res == -1)
  21. {
  22. perror("semaphore intitialization failed\n");
  23. exit(EXIT_FAILURE);
  24. }
  25. //初始化信号量,初始值为1
  26. res = sem_init(&sem_add, 0, 1);
  27. if(res == -1)
  28. {
  29. perror("semaphore intitialization failed\n");
  30. exit(EXIT_FAILURE);
  31. }
  32. //创建线程,并把msg作为线程函数的参数
  33. res = pthread_create(&thread, NULL, thread_func, msg);
  34. if(res != 0)
  35. {
  36. perror("pthread_create failed\n");
  37. exit(EXIT_FAILURE);
  38. }
  39. //输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n”
  40. printf("Input some text. Enter 'end'to finish...\n");
  41. sem_wait(&sem_add);
  42. while(strcmp("end\n", msg) != 0)
  43. {
  44. if(strncmp("TEST", msg, 4) == 0)
  45. {
  46. strcpy(msg, "copy_data\n");
  47. sem_post(&sem);
  48. //把sem_add的值减1,即等待子线程处理完成
  49. sem_wait(&sem_add);
  50. }
  51. fgets(msg, MSG_SIZE, stdin);
  52. //把信号量加1
  53. sem_post(&sem);
  54. //把sem_add的值减1,即等待子线程处理完成
  55. sem_wait(&sem_add);
  56. }
  57. printf("Waiting for thread to finish...\n");
  58. //等待子线程结束
  59. res = pthread_join(thread, &thread_result);
  60. if(res != 0)
  61. {
  62. perror("pthread_join failed\n");
  63. exit(EXIT_FAILURE);
  64. }
  65. printf("Thread joined\n");
  66. //清理信号量
  67. sem_destroy(&sem);
  68. sem_destroy(&sem_add);
  69. exit(EXIT_SUCCESS);
  70. }
  71. void* thread_func(void *msg)
  72. {
  73. char *ptr = msg;
  74. //把信号量减1
  75. sem_wait(&sem);
  76. while(strcmp("end\n", msg) != 0)
  77. {
  78. int i = 0;
  79. //把小写字母变成大写
  80. for(; ptr[i] != '\0'; ++i)
  81. {
  82. if(ptr[i] >= 'a' && ptr[i] <= 'z')
  83. {
  84. ptr[i] -= 'a' - 'A';
  85. }
  86. }
  87. printf("You input %d characters\n", i-1);
  88. printf("To Uppercase: %s\n", ptr);
  89. //把信号量加1,表明子线程处理完成
  90. sem_post(&sem_add);
  91. //把信号量减1
  92. sem_wait(&sem);
  93. }
  94. sem_post(&sem_add);
  95. //退出线程
  96. pthread_exit(NULL);
  97. }

其运行结果如下:

 
分析:这里我们多使用了一个信号量sem_add,并把它的初值赋为1,在主线程在使用sem_wait来等待子线程处理完全,由于它的初值为1,所以主线程第一次调用sem_wait总是立即返回,而第二次调用则需要等待子线程处理完成之后。而在子线程中,若处理完成就会马上使用sem_post来增加信号量的值,使主线程中的sem_wait马上返回并执行紧接下面的代码。从运行结果来看,运行终于正常了。注意,在线程函数中,信号量sem和sem_add使用sem_wait和sem_post函数的次序,它们的次序不能错乱,否则在输入end时,可能运行不正常,子线程不能正常退出,从而导致程序不能退出。
 
至于使用互斥量的方法,将会在下篇文章:Linux多线程——使用互斥量同步线程中详细介绍。

linux 线程的同步 三 (内存信号量的使用)的更多相关文章

  1. Linux并发与同步专题 (3) 信号量

    关键词:Semaphore.down()/up(). <Linux并发与同步专题 (1)原子操作和内存屏障> <Linux并发与同步专题 (2)spinlock> <Li ...

  2. linux 线程的同步 一 (互斥量和信号量)

    互斥量(Mutex) 互斥量表现互斥现象的数据结构,也被当作二元信号灯.一个互斥基本上是一个多任务敏感的二元信号,它能用作同步多任务的行为,它常用作保护从中断来的临界段代码并且在共享同步使用的资源. ...

  3. linux线程间同步方式总结梳理

    线程间一般无需特别的手段进行通信,由于线程间能够共享数据结构,也就是一个全局变量能够被两个线程同时使用.只是要注意的是线程间须要做好同步! 使用多线程的理由: 1. 一个是和进程相比,它是一种非常&q ...

  4. Linux线程间同步的几种方式

    信号量 信号量强调的是线程(或进程)间的同步:"信号量用在多线程多任务同步的,一个线程完成了某一个动作就通过信号量告诉别的线程,别的线程再进行某些动作(大家都在sem_wait的时候,就阻塞 ...

  5. 线程间同步之 semaphore(信号量)

    原文地址:http://www.cnblogs.com/yuqilin/archive/2011/10/16/2214429.html semaphore 可用于进程间同步也可用于同一个进程间的线程同 ...

  6. linux线程(二)内存释放

    linux线程有两种模式joinable和unjoinable. joinable线程:系统会保存线程资源(栈.ID.退出状态等)直到线程退出并且被其他线程join. unjoinable线程:系统会 ...

  7. linux线程间同步方式汇总

    抽空做了下linux所有线程间同步方式的汇总(原生的),包含以下几个: 1, mutex 2, condition variable 3, reader-writer lock 4, spin loc ...

  8. linux 线程的同步 二 (互斥锁和条件变量)

    互斥锁和条件变量 为了允许在线程或进程之间共享数据,同步时必须的,互斥锁和条件变量是同步的基本组成部分. 1.互斥锁 互斥锁是用来保护临界区资源,实际上保护的是临界区中被操纵的数据,互斥锁通常用于保护 ...

  9. linux线程间同步(1)读写锁

    读写锁比mutex有更高的适用性,能够多个线程同一时候占用读模式的读写锁.可是仅仅能一个线程占用写模式的读写锁. 1. 当读写锁是写加锁状态时,在这个锁被解锁之前,全部试图对这个锁加锁的线程都会被堵塞 ...

随机推荐

  1. mis权限系统

    在mis中开发,主要目的是有一个统一的权限管理(即r360.right表),以及一个统一的系统和界面供后台配置管理 1.数据库准备工作: mis后台涉及表: right表是权限操作表,role_rig ...

  2. css 基础 - 3

    css 基础 - 3 20161128   一. 元素的距离计算 1,两个水平方向的容器s1,s2之间的距离计算为s: s = s1的margin-right + s2的margin-left(+默认 ...

  3. CSS 再学习,基础篇

    语法 h1 {color:red; font-size:14px;} 共享声明 h1,h2,h3,h4,h5,h6 { color: green; } 继承 通过 CSS 继承,子元素将继承最高级元素 ...

  4. 解决xshell乱码问题

    如下图,xshell在执行命令时显示乱码 解决办法: 文件—属性—终端,将编码改成Unicode即可 参考文章 https://blog.csdn.net/yueloveme/article/deta ...

  5. java--Quartz 定时执行

    第一步:引包(Maven) <!-- 定时任务 --> <dependency> <groupId>org.quartz-scheduler</groupId ...

  6. 使用POI设置导出的EXCEL锁定指定的单元格

    注:要锁定单元格需先为此表单设置保护密码,设置之后此表单默认为所有单元格锁定,可使用setLocked(false)为指定单元格设置不锁定. sheet.protectSheet("&quo ...

  7. html中用变量作为django字典的键值

    若字典为dic={'name': Barbie, 'age': 20},则在html中dic.name为Barbie,dic.age为20. 但若字典为dic={'Barbie': 1, 'Roger ...

  8. 019PHP基础知识——函数(二)

    <?php /** * 变量的作用范围 * 函数体内的变量只作用于函数体内. */ /*$bbs="bbs.blog.com"; function say(){ $bbs=& ...

  9. Jquery倒计时源码分享

    在静态页添加显示倒计时的容器,并引用下面脚本,代入时间参数即可使用. timeoutDate——到期时间,时间格式为2014/01/01或2014/1/1 D——天 H——小时 M——分钟 S——秒 ...

  10. 【转】powerdesigner 数据类型与数据库数据类型对应

    The following numeric data types are available: Standard data type DBMS-specific physical data type ...