信号量、同步这些名词在进程间通信时就已经说过,在这里它们的意思是相同的,只不过是同步的对象不同而已。但是下面介绍的信号量的接口是用于线程的信号量,注意不要跟用于进程间通信的信号量混淆,关于用于进程间通信的信号量的详细介绍可以参阅我的另一篇博文: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. 【2017-06-20】Linux应用开发工程师C/C++面试问题记录之一:Linux多线程程序的同步问题

    参考之一:Linux 线程同步的三种方法 链接地址:http://www.cnblogs.com/eleclsc/p/5838790.html 简要回答: Linux下线程同步最常用的三种方法就是互斥 ...

  2. Linux多线程编程-信号量

    在Linux中.信号量API有两组.一组是多进程编程中的System V IPC信号量.另外一组是我们要讨论的POSIX信号量. 这两组接口类似,但不保证互换.POSIX信号量函数都已sem_开头,并 ...

  3. Linux 多线程条件变量同步

    条件变量是线程同步的另一种方式,实际上,条件变量是信号量的底层实现,这也就意味着,使用条件变量可以拥有更大的自由度,同时也就需要更加小心的进行同步操作.条件变量使用的条件本身是需要使用互斥量进行保护的 ...

  4. Linux多线程实践(9) --简单线程池的设计与实现

    线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收.所以 ...

  5. linux 共享内存 信号量 同步

    这篇文章将讲述别一种进程间通信的机制——信号量.注意请不要把它与之前所说的信号混淆起来,信号与信号量是不同的两种事物.有关信号的更多内容,可以阅读我的另一篇文章:Linux进程间通信——使用信号.下面 ...

  6. 多线程&定时器Timer&同步&线程通信&ThreadLocal

    1.多线程 线程状态分为:新建状态.就绪状态.运行状态.阻塞状态.死亡状态 对象等待池的阻塞状态:运行状态执行了wait方法 对向锁池的阻塞状态:试图获得某个同步锁,已经被其他线程占用,就会放到对象的 ...

  7. linux多线程学习笔记五--线程安全【转】

    转自:http://blog.csdn.net/kkxgx/article/details/7506085 版权声明:本文为博主原创文章,未经博主允许不得转载. 一,线程安全基础 一个函数被称为线程安 ...

  8. Linux 多线程环境下 进程线程终止函数小结(转)

    pthread_kill: pthread_kill与kill有区别,是向线程发送signal.,大部分signal的默认动作是终止进程的运行,所以,我们才要用signal()去抓信号并加上处理函数. ...

  9. Linux多线程——使用互斥量同步线程

    前文再续,书接上一回,在上一篇文章: Linux多线程——使用信号量同步线程中,我们留下了一个如何使用互斥量来进行线程同步的问题,本文将会给出互斥量的详细解说,并用一个互斥量解决上一篇文章中,要使用两 ...

随机推荐

  1. Spring Boot中的自定义start pom

    start pom是springboot中提供的简化企业级开发绝大多数场景的一个工具,利用好strat pom就可以消除相关技术的配置得到自动配置好的Bean. 举个例子,在一般使用中,我们使用基本的 ...

  2. 洛谷P4035 球形空间产生器 [JSOI2008] 高斯消元

    正解:高斯消元 解题报告: 链接! 昂开始看到以为是,高斯消元板子题? 开始很容易想到的是,虽然是多维但是可以类比二维三维列出式子嘛 但是高斯消元是只能处理一元问题的啊,,,辣怎么处理呢 对的这就是这 ...

  3. 【Python】如何切换浏览器的tap页?

    当点击浏览器当前页面中的某个链接后自动弹出一个新的浏览器的tap页面时,浏览器正常都会在当前操作的tap页面右1位置打开新弹出的tap页. 当前selenium还不具备自动切换页面后窗口切换功能.需要 ...

  4. 【Loadrunner】【浙江移动项目手写代码】代码备份

    vuser_init(){        lr_start_transaction("login"); web_url("10.78.224.136:8080" ...

  5. Shell初学(三)传参

    一. 脚本代码:test.sh echo "Shell 传递参数实例!"; echo "执行的文件名:$0"; echo "第一个参数为:$1&quo ...

  6. 第十八篇:融汇贯通--谈USB Video Class驱动

    USB Video Class驱动是WINDOWS系统包含的一个针对于USB VIDEO 类的驱动程序. 好多project师都做过USB VIDEO设备端的开发, 基本的工作内容为: 使用FIRMW ...

  7. office 2016 install(office2016组件自定义安装激活程序) v5.9.3中文绿色版

    下载地址  http://www.ddooo.com/softdown/71741.htm#dltab office 2016 install是目前下载office2016和office2016组件最 ...

  8. PHPExcel 基本用法详解

    .header header("Content-Type:application/vnd.ms-excel"); header("Content-Disposition: ...

  9. Scala集合类详解

    对scala中的集合类虽然有使用,但是一直处于一知半解的状态.尤其是与java中各种集合类的混合使用,虽然用过很多次,但是一直也没有做比较深入的了解与分析.正好趁着最近项目的需要,加上稍微有点时间,特 ...

  10. [py]flask从0到1-模板/增删改查

    flask知识点 1.后端渲染html到前端 render_template 2.后端获取前端数据 request.args.get 3.前端获取后端数据 模板 4.警示消息 flash {{ get ...