Linux多线程--使用信号量同步线程【转】

本文转载自:http://blog.csdn.net/ljianhui/article/details/10813469

信号量、同步这些名词在进程间通信时就已经说过,在这里它们的意思是相同的,只不过是同步的对象不同而已。但是下面介绍的信号量的接口是用于线程的信号量,注意不要跟用于进程间通信的信号量混淆,关于用于进程间通信的信号量的详细介绍可以参阅我的另一篇博文:Linux进程间通信——使用信号量。相似地,线程同步是控制线程执行和访问临界区域的方法。

一、什么是信号量

线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变量,它可以被增加或减少,但对其的关键访问被保证是原子操作。如果一个程序中有多个线程试图改变一个信号量的值,系统将保证所有的操作都将依次进行。

而只有0和1两种取值的信号量叫做二进制信号量,在这里将重点介绍。而信号量一般常用于保护一段代码,使其每次只被一个执行线程运行。我们可以使用二进制信号量来完成这个工作。

二、信号量的接口和使用

信号量的函数都以sem_开头,线程中使用的基本信号量函数有4个,它们都声明在头文件semaphore.h中。

1、sem_init函数

该函数用于创建信号量,其原型如下:

[cpp] view plain copy

print?

  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,它们之间不会互相干扰。它的原型如下:

[cpp] view plain copy

print?

  1. int sem_wait(sem_t *sem);

sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.

3、sem_post函数

该函数用于以原子操作的方式将信号量的值加1。它的原型如下:

[cpp] view plain copy

print?

  1. int sem_post(sem_t *sem);

与sem_wait一样,sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.

4、sem_destroy函数

该函数用于对用完的信号量的清理。它的原型如下:

[cpp] view plain copy

print?

  1. int sem_destroy(sem_t *sem);

成功时返回0,失败时返回-1.

三、使用信号量同步线程

下面以一个简单的多线程程序来说明如何使用信号量进行线程同步。在主线程中,我们创建子线程,并把数组msg作为参数传递给子线程,然后主线程等待直到有文本输入,然后调用sem_post来增加信号量的值,这样就会立刻使子线程从sem_wait的等待中返回并开始执行。线程函数在把字符串的小写字母变成大写并统计输入的字符数量之后,它再次调用sem_wait并再次被阻塞,直到主线程再次调用sem_post增加信号量的值。

[cpp] view plain copy

print?

  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循环中的代码,如下:

[cpp] view plain copy

print?

  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,源代码如下:

[cpp] view plain copy

print?

  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多线程——使用互斥量同步线程中详细介绍。

时间: 2024-10-06 08:07:03

Linux多线程--使用信号量同步线程【转】的相关文章

深入理解计算机系统——第12章:用信号量同步线程

用信号量同步线程: 同步错误: 一般而言你没有办法预测操作系统是否将你的线程选择一个正确的顺序执行. 12.5.1 进度图 (1)进度图:将n个并发线程的执行模型化为一条n维笛卡尔空间中的轨迹线. (2)每条轴k对应着线程k的进度. (3)每个点Ik代表着k线程已完成指令Ik这一个状态,原点处代表初始状态. (4)进度图是指令执行模型化为从一个状态到另一个状态的转换,两条指令不能在同一时刻完成,对角线不允许的.

【2017-06-20】Linux应用开发工程师C/C++面试问题之一:Linux多线程程序的同步问题

参考之一:Linux 线程同步的三种方法 链接地址:http://www.cnblogs.com/eleclsc/p/5838790.html 简要回答: Linux下线程同步最常用的三种方法就是互斥锁.条件变量及信号量. 互斥锁通过锁机制来实现线程间的同步,锁机制是同一时刻只允许一个线程执行一个关键部分的代码. 条件变量是用来等待而不是用来上锁的,条用来自动阻塞一个线程,直到某特殊情况发生为止,通常条件变量和互斥锁同时使用. 线程的信号量与进程间通信中使用的信号量的概念是一样,它是一种特殊的变

Linux多线程编程-信号量

在Linux中,信号量API有两组,一组是多进程编程中的System V IPC信号量:另外一组是我们要讨论的POSIX信号量.这两组接口类似,但不保证互换.POSIX信号量函数都已sem_开头,并不像大多数线程函数那样以pthread_开头,常用的有以下5个: #include <semaphore.h> int sem_init(sem_t* sem, int pshared, unsigned int value); int sem_destroy(sem_t *sem); int se

38.线程三--&gt;多线程数据安全和同步线程的方法

学习要点 多线程数据安全 同步线程的方法 class MyThread implements Runnable{//MyThread 实现Runnable接口 int i = 100; public void run(){ //复写run方法 while(true){ synchronized(this){  //线程同步代码 //Thread.currentThread()获取当前这段代码正在哪个线程运行 System.out.println(Thread.currentThread().ge

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

线程池的技术背景 在面向对象编程中,创建和销毁对象是很费时间的,因为创建一个对象要获取内存资源或者其它更多资源.在Java中更是如此,虚拟机将试图跟踪每一个对象,以便能够在对象销毁后进行垃圾回收.所以提高服务程序效率的一个手段就是尽可能减少创建和销毁对象的次数,特别是一些很耗资源的对象创建和销毁.如何利用已有对象来服务(不止一个不同的任务)就是一个需要解决的关键问题,其实这就是一些"池化资源"技术产生的原因.比如大家所熟悉的数据库连接池正是遵循这一思想而产生的,本文将介绍的线程池技术同

(转载)Linux 多线程条件变量同步

条件变量是线程同步的另一种方式,实际上,条件变量是信号量的底层实现,这也就意味着,使用条件变量可以拥有更大的自由度,同时也就需要更加小心的进行同步操作.条件变量使用的条件本身是需要使用互斥量进行保护的,线程在改变条件状态之前必须首先锁住互斥量,其他线程在获得互斥量之前不会察觉到这种改变,因为互斥量必须在锁定之后才能计算条件. 模型 #include<pthread.h> pthread_cond_t cond //准备条件变量 pthread_cond_t cond = PTHREAD_CON

Linux多线程--使用互斥量同步线程【转】

本文转载自:http://blog.csdn.net/ljianhui/article/details/10875883 前文再续,书接上一回,在上一篇文章:Linux多线程——使用信号量同步线程中,我们留下了一个如何使用互斥量来进行线程同步的问题,本文将会给出互斥量的详细解说,并用一个互斥量解决上一篇文章中,要使用两个信号量才能解决的只有子线程结束了对输入的处理和统计后,主线程才能继续执行的问题. 一.什么是互斥量 互斥量是另一种用于多线程中的同步访问方法,它允许程序锁住某个对象,使得每次只能

Linux多线程编程-条件变量

条件变量 如果说线程间的互斥锁是用来同步共享数据的访问的话,那么条件变量是用于线程之间共享数据的值.条件变量提供了一种线程之间的通知机制,当某个共享数据达到某个值时,唤醒等待这个共享数据的线程.条件变量相关函数主要 有5个: #include <pthread.h> int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *cond_attr); int pthread_cond_destroy(pthread_

Linux多线程编程-互斥锁

互斥锁 多线程编程中,(多线程编程)可以用互斥锁(也称互斥量)可以用来保护关键代码段,以确保其独占式的访问,这有点像二进制信号量.POSIX互斥锁相关函数主要有以下5个: #include <pthread.h> int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *mutexattr); int pthread_mutex_destroy(pthread_mutex_t *mutex); int p