Linux多线程——使用信号量同步线程

http://blog.csdn.net/ljianhui/article/details/10813469/

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

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

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

二、信号量的接口和使用

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

1、sem_init函数
该函数用于创建信号量,其原型如下:
[cpp] view plain copy
  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
  1. int sem_wait(sem_t *sem);  
sem指向的对象是由sem_init调用初始化的信号量。调用成功时返回0,失败返回-1.

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

4、sem_destroy函数
该函数用于对用完的信号量的清理。它的原型如下:
[cpp] view plain copy
  1. int sem_destroy(sem_t *sem);  
成功时返回0,失败时返回-1.

三、使用信号量同步线程

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

[cpp] view plain copy
  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. //线程函数  
  9. void *thread_func(void *msg);  
  10. sem_t sem;//信号量  
  11.   
  12. #define MSG_SIZE 512  
  13.   
  14. int main()  
  15. {  
  16.     int res = -1;  
  17.     pthread_t thread;  
  18.     void *thread_result = NULL;  
  19.     char msg[MSG_SIZE];  
  20.     //初始化信号量,其初值为0  
  21.     res = sem_init(&sem, 0, 0);  
  22.     if(res == -1)  
  23.     {  
  24.         perror("semaphore intitialization failed\n");  
  25.         exit(EXIT_FAILURE);  
  26.     }  
  27.     //创建线程,并把msg作为线程函数的参数  
  28.     res = pthread_create(&thread, NULL, thread_func, msg);  
  29.     if(res != 0)  
  30.     {  
  31.         perror("pthread_create failed\n");  
  32.         exit(EXIT_FAILURE);  
  33.     }  
  34.     //输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n”  
  35.     printf("Input some text. Enter 'end'to finish...\n");  
  36.     while(strcmp("end\n", msg) != 0)  
  37.     {  
  38.         fgets(msg, MSG_SIZE, stdin);  
  39.         //把信号量加1  
  40.         sem_post(&sem);  
  41.     }  
  42.   
  43.     printf("Waiting for thread to finish...\n");  
  44.     //等待子线程结束  
  45.     res = pthread_join(thread, &thread_result);  
  46.     if(res != 0)  
  47.     {  
  48.         perror("pthread_join failed\n");  
  49.         exit(EXIT_FAILURE);  
  50.     }  
  51.     printf("Thread joined\n");  
  52.     //清理信号量  
  53.     sem_destroy(&sem);  
  54.     exit(EXIT_SUCCESS);  
  55. }  
  56.   
  57. void* thread_func(void *msg)  
  58. {  
  59.     //把信号量减1  
  60.     sem_wait(&sem);  
  61.     char *ptr = msg;  
  62.     while(strcmp("end\n", msg) != 0)  
  63.     {  
  64.         int i = 0;  
  65.         //把小写字母变成大写  
  66.         for(; ptr[i] != '\0'; ++i)  
  67.         {  
  68.             if(ptr[i] >= 'a' && ptr[i] <= 'z')  
  69.             {  
  70.                 ptr[i] -= 'a' - 'A';  
  71.             }  
  72.         }  
  73.         printf("You input %d characters\n", i-1);  
  74.         printf("To Uppercase: %s\n", ptr);  
  75.         //把信号量减1  
  76.         sem_wait(&sem);  
  77.     }  
  78.     //退出线程  
  79.     pthread_exit(NULL);  
  80. }  
运行结果如下:



从运行的结果来看,这个程序的确是同时在运行两个线程,一个控制输入,另一个控制处理统计和输出。

四、分析此信号量同步程序的缺陷
但是这个程序有一点点的小问题,就是这个程序依赖接收文本输入的时间足够长,这样子线程才有足够的时间在主线程还未准备好给它更多的单词去处理和统计之前处理和统计出工作区中字符的个数。所以当我们连续快速地给它两组不同的单词去统计时,子线程就没有足够的时间支执行,但是信号量已被增加不止一次,所以字符统计线程(子线程)就会反复处理和统计字符数目,并减少信号量的值,直到它再次变成0为止。

为了更加清楚地说明上面所说的情况,修改主线程的while循环中的代码,如下:
[cpp] view plain copy
  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
  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.   
  9. //线程函数  
  10. void *thread_func(void *msg);  
  11. sem_t sem;//信号量  
  12. sem_t sem_add;//增加的信号量  
  13.   
  14.   
  15. #define MSG_SIZE 512  
  16.   
  17.   
  18. int main()  
  19. {  
  20.     int res = -1;  
  21.     pthread_t thread;  
  22.     void *thread_result = NULL;  
  23.     char msg[MSG_SIZE];  
  24.     //初始化信号量,初始值为0  
  25.     res = sem_init(&sem, 0, 0);  
  26.     if(res == -1)  
  27.     {  
  28.         perror("semaphore intitialization failed\n");  
  29.         exit(EXIT_FAILURE);  
  30.     }  
  31.     //初始化信号量,初始值为1  
  32.     res = sem_init(&sem_add, 0, 1);  
  33.     if(res == -1)  
  34.     {  
  35.         perror("semaphore intitialization failed\n");  
  36.         exit(EXIT_FAILURE);  
  37.     }  
  38.     //创建线程,并把msg作为线程函数的参数  
  39.     res = pthread_create(&thread, NULL, thread_func, msg);  
  40.     if(res != 0)  
  41.     {  
  42.         perror("pthread_create failed\n");  
  43.         exit(EXIT_FAILURE);  
  44.     }  
  45.     //输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n”  
  46.     printf("Input some text. Enter 'end'to finish...\n");  
  47.       
  48.     sem_wait(&sem_add);  
  49.     while(strcmp("end\n", msg) != 0)  
  50.     {  
  51.         if(strncmp("TEST", msg, 4) == 0)  
  52.         {  
  53.             strcpy(msg, "copy_data\n");  
  54.             sem_post(&sem);  
  55.             //把sem_add的值减1,即等待子线程处理完成  
  56.             sem_wait(&sem_add);  
  57.         }  
  58.         fgets(msg, MSG_SIZE, stdin);  
  59.         //把信号量加1  
  60.         sem_post(&sem);  
  61.         //把sem_add的值减1,即等待子线程处理完成  
  62.         sem_wait(&sem_add);  
  63.     }  
  64.   
  65.   
  66.     printf("Waiting for thread to finish...\n");  
  67.     //等待子线程结束  
  68.     res = pthread_join(thread, &thread_result);  
  69.     if(res != 0)  
  70.     {  
  71.         perror("pthread_join failed\n");  
  72.         exit(EXIT_FAILURE);  
  73.     }  
  74.     printf("Thread joined\n");  
  75.     //清理信号量  
  76.     sem_destroy(&sem);  
  77.     sem_destroy(&sem_add);  
  78.     exit(EXIT_SUCCESS);  
  79. }  
  80.   
  81.   
  82. void* thread_func(void *msg)  
  83. {  
  84.     char *ptr = msg;  
  85.     //把信号量减1  
  86.     sem_wait(&sem);  
  87.     while(strcmp("end\n", msg) != 0)  
  88.     {  
  89.         int i = 0;  
  90.         //把小写字母变成大写  
  91.         for(; ptr[i] != '\0'; ++i)  
  92.         {  
  93.             if(ptr[i] >= 'a' && ptr[i] <= 'z')  
  94.             {  
  95.                 ptr[i] -= 'a' - 'A';  
  96.             }  
  97.         }  
  98.         printf("You input %d characters\n", i-1);  
  99.         printf("To Uppercase: %s\n", ptr);  
  100.         //把信号量加1,表明子线程处理完成  
  101.         sem_post(&sem_add);  
  102.         //把信号量减1  
  103.         sem_wait(&sem);  
  104.     }  
  105.     sem_post(&sem_add);  
  106.     //退出线程  
  107.     pthread_exit(NULL);  
  108. }  
其运行结果如下:


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

至于使用互斥量的方法,将会在下篇文章:Linux多线程——使用互斥量同步线程中详细介绍。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/384220.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++11新特性之八——函数对象function

http://www.cnblogs.com/yyxt/p/3987717.html 详细请看《C Primer plus》(第六版中文版) http://www.cnblogs.com/lvpengms/archive/2011/02/21/1960078.html 备注&#xff1a; 函数对象&#xff1a; 尽管函数指针被广泛用于实现函数回调&#xff0c;但C还提供了一个重要的实现…

从零开始学C++之STL(八):函数对象、 函数对象与容器、函数对象与算法

http://blog.csdn.net/jnu_simba/article/details/9500219 一、函数对象 1、函数对象&#xff08;function object&#xff09;也称为仿函数&#xff08;functor&#xff09; 2、一个行为类似函数的对象&#xff0c;它可以没有参数&#xff0c;也可以带有若干参数。 3、任何重载…

树状数组初步理解

学习树状数组已经两周了&#xff0c;之前偷懒一直没有写&#xff0c;赶紧补上防止自己忘记&#xff08;虽然好像已经忘得差不多了&#xff09;。 作为一种经常处理区间问题的数据结构&#xff0c;它和线段树、分块一样&#xff0c;核心就是将区间分成许多个小区间然后通过对大区…

Linux socket编程(二) 服务器与客户端的通信

http://www.cnblogs.com/-Lei/archive/2012/09/04/2670964.html上一篇写了对套接字操作的封装&#xff0c;这一节使用已封装好的Socket类实现服务器与客户端的通信&#xff08;Socket的定义见上篇Socket.h) 服务器端&#xff1a; ServerSocket.h #ifndef SERVERSOCKET_H #defin…

UNIX网络编程:I/O复用技术(select、poll、epoll)

http://blog.csdn.net/dandelion_gong/article/details/51673085 Unix下可用的I/O模型一共有五种&#xff1a;阻塞I/O 、非阻塞I/O 、I/O复用 、信号驱动I/O 、异步I/O。此处我们主要介绍第三种I/O符复用。 I/O复用的功能&#xff1a;如果一个或多个I/O条件满足&#xff08;输…

解决iex -S mix报错

执行iex -S mix命令的时候会遇到如下错误&#xff1a; 执行 mix deps.get 然后就可以运行 iex -S mix了 其中&#xff0c;有可能会出现 按照其网站下载相应文件&#xff0c;复制到项目根目录下&#xff0c;然后执行命令&#xff08;mix local.rebar rebar ./rebar&#xff09;即…

Anker—工作学习笔记

http://www.cnblogs.com/Anker/archive/2013/08/17/3263780.html 1、基本知识 epoll是在2.6内核中提出的&#xff0c;是之前的select和poll的增强版本。相对于select和poll来说&#xff0c;epoll更加灵活&#xff0c;没有描述符限制。epoll使用一个文件描述符管理多个描述符&am…

Linux网络编程——tcp并发服务器(I/O复用之select

http://blog.csdn.net/lianghe_work/article/details/46519633 与多线程、多进程相比&#xff0c;I/O复用最大的优势是系统开销小&#xff0c;系统不需要建立新的进程或者线程&#xff0c;也不必维护这些线程和进程。 代码示例&#xff1a; [csharp] view plaincopy #include &…

Linux下的I/O复用与epoll详解

http://www.cnblogs.com/lojunren/p/3856290.html 前言 I/O多路复用有很多种实现。在linux上&#xff0c;2.4内核前主要是select和poll&#xff0c;自Linux 2.6内核正式引入epoll以来&#xff0c;epoll已经成为了目前实现高性能网络服务器的必备技术。尽管他们的使用方法不尽相…

数据结构--顺序栈和链式栈

http://www.cnblogs.com/jingliming/p/4602458.html 栈是一种限定只在表尾进行插入或删除操作,栈也是线性表表头称为栈的底部,表尾称为栈的顶部,表为空称为空栈&#xff0c;栈又称为后进先出的线性表,栈也有两种表示:顺序栈与链式栈顺序栈是利用一组地址连续的存储单元&#xf…

数据结构--双链表的创建和操作

http://www.cnblogs.com/jingliming/p/4602144.html#0-tsina-1-42616-397232819ff9a47a7b7e80a40613cfe1 一、双向链表的定义 双向链表也叫双链表&#xff0c;是链表的一种&#xff0c;它的每个数据结点中都有两个指针&#xff0c;分别指向直接后继和直接前驱。所以&#xff0c…

MYSQL错误代码#1045 Access denied for user 'root'@'localhost'

http://blog.csdn.net/lykezhan/article/details/70880845 遇到MYSQL“错误代码#1045 Access denied for user rootlocalhost (using password:YES)” 需要重置root账号权限密码&#xff0c;这个一般还真不好解决。 不过&#xff0c;这几天调试的时候真的遇到了这种问题&#x…

常量变量以及循环

常量 1.三目运算词 三字母词表达字符???([??)]??<{??>} 2.循环 1).数组元素以及变量在内存中的分配顺序 2)goto语句应用 //电脑关机程序 #include<stdio.h> #include <stdlib.h> #include <string.h> #include <windows.h> int ma…

Linux 环境 C语言 操作MySql 的接口范例

http://www.cnblogs.com/wunaozai/p/3876134.html 接上一小节&#xff0c;本来是计划这一节用来讲数据库的增删改查&#xff0c;但是在实现的过程中&#xff0c;出现了一点小问题&#xff0c;也不是技术的问题&#xff0c;就是在字符界面上比较不好操作。比如要注册一个帐号&a…

数组相关运算

数组的初始化 数组及指针在内存中的存储 一维数组在内存中的存储 有关数组的运算 //一维数组 int a[] {1,2,3,4}; printf("%d\n",sizeof(a));//16这里的a表示的是整个数组,计算出的是整个数组的大小,单位为byte printf("%d\n",sizeof(a 0));/*a没有单独…

gets fgets 区别

http://www.cnblogs.com/aexin/p/3908003.html 1. gets与fgets gets函数原型&#xff1a;char*gets(char*buffer);//读取字符到数组&#xff1a;gets(str);str为数组名。 gets函数功能&#xff1a;从键盘上输入字符&#xff0c;直至接受到换行符或EOF时停止&#xff0c;并将读取…

Shuffle'm Up——简单模拟

【题目描述】 A common pastime for poker players at a poker table is to shuffle stacks of chips. Shuffling chips is performed by starting with two stacks of poker chips, S1 and S2, each stack containing C chips. Each stack may contain chips of several diff…

Fire!——两个BFS

【题目描述】 【题目分析】 看到题目后很清楚是两个BFS&#xff0c;可是我觉得对于火的BFS可以转换成判断&#xff0c;我的做法是将火的位置全部记录下来&#xff0c;然后判断某个位置距离每个火的步数是否小于当前步数&#xff0c;可是错了&#xff0c;还不清楚为什么&#x…

函数调用过程(栈桢)

栈桢 首先来看一段代码 #include<stdio.h> int add(int x, int y) {int z x y;return z; } int main() {int a 10;int b 20;int ret add(a, b);printf("ret %d\n",ret);return 0; } 此处是为了给a,b分别开辟空间,这时栈桢如图所示 两条push命令将a,b变…

整型数据存储

//代码1 #include<stdio.h> int main() {char a -1;signed char b -1;unsigned char c -1;printf("a %d, b %d, c %d", a, b, c);return 0; } 1000 0000 0000 0001 -> -1源码 1111 1111 1111 1110 -> -1反码 1111 1111 1111 1111 -> -1补码 对于…