侯体宗的博客
  • 首页
  • Hyperf版
  • beego仿版
  • 人生(杂谈)
  • 技术
  • 关于我
  • 更多分类
    • 文件下载
    • 文字修仙
    • 中国象棋ai
    • 群聊
    • 九宫格抽奖
    • 拼图
    • 消消乐
    • 相册

详解Linux多线程使用信号量同步

linux  /  管理员 发布于 7年前   215

信号量、同步这些名词在进程间通信时就已经说过,在这里它们的意思是相同的,只不过是同步的对象不同而已。但是下面介绍的信号量的接口是用于线程的信号量,注意不要跟用于进程间通信的信号量混淆。

一、什么是信号量

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

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

二、信号量的接口和使用

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

1、sem_init函数

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

int sem_init(sem_t *sem, int pshared, unsigned int value); 

该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示这个信号量是当前进程的局部信号量,否则信号量就可以在多个进程之间共享,value为sem的初始值。调用成功时返回0,失败返回-1.

2、sem_wait函数

该函数用于以原子操作的方式将信号量的值减1。原子操作就是,如果两个线程企图同时给一个信号量加1或减1,它们之间不会互相干扰。它的原型如下:

int sem_wait(sem_t *sem); 

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

3、sem_post函数

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

int sem_post(sem_t *sem); 

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

4、sem_destroy函数

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

int sem_destroy(sem_t *sem); 

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

三、使用信号量同步线程

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

#include <unistd.h> #include <pthread.h> #include <semaphore.h> #include <stdlib.h> #include <stdio.h> #include <string.h>  //线程函数 void *thread_func(void *msg); sem_t sem;//信号量  #define MSG_SIZE 512  int main() {   int res = -1;   pthread_t thread;   void *thread_result = NULL;   char msg[MSG_SIZE];   //初始化信号量,其初值为0   res = sem_init(&sem, 0, 0);   if(res == -1)   {     perror("semaphore intitialization failed\n");     exit(EXIT_FAILURE);   }   //创建线程,并把msg作为线程函数的参数   res = pthread_create(&thread, NULL, thread_func, msg);   if(res != 0)   {     perror("pthread_create failed\n");     exit(EXIT_FAILURE);   }   //输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n”   printf("Input some text. Enter 'end'to finish...\n");   while(strcmp("end\n", msg) != 0)   {     fgets(msg, MSG_SIZE, stdin);     //把信号量加1     sem_post(&sem);   }    printf("Waiting for thread to finish...\n");   //等待子线程结束   res = pthread_join(thread, &thread_result);   if(res != 0)   {     perror("pthread_join failed\n");     exit(EXIT_FAILURE);   }   printf("Thread joined\n");   //清理信号量   sem_destroy(&sem);   exit(EXIT_SUCCESS); }  void* thread_func(void *msg) {   //把信号量减1   sem_wait(&sem);   char *ptr = msg;   while(strcmp("end\n", msg) != 0)   {     int i = 0;     //把小写字母变成大写     for(; ptr[i] != '\0'; ++i)     {       if(ptr[i] >= 'a' && ptr[i] <= 'z')       {         ptr[i] -= 'a' - 'A';       }     }     printf("You input %d characters\n", i-1);     printf("To Uppercase: %s\n", ptr);     //把信号量减1     sem_wait(&sem);   }   //退出线程   pthread_exit(NULL); } 

运行结果如下:

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

四、分析此信号量同步程序的缺陷

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

为了更加清楚地说明上面所说的情况,修改主线程的while循环中的代码,如下:

printf("Input some text. Enter 'end'to finish...\n"); while(strcmp("end\n", msg) != 0) {   if(strncmp("TEST", msg, 4) == 0)   {     strcpy(msg, "copy_data\n");     sem_post(&sem);   }   fgets(msg, MSG_SIZE, stdin);   //把信号量加1   sem_post(&sem); } 

重新编译程序,此时运行结果如下:

当我们输入TEST时,主线程向子线程提供了两个输入,一个是来自键盘的输入,一个来自主线程复数据到msg中,然后从运行结果可以看出,运行出现了异常,没有处理和统计从键盘输入TEST的字符串而却对复制的数据作了两次处理。原因如上面所述。

五、解决此缺陷的方法

解决方法有两个,一个就是再增加一个信号量,让主线程等到子线程处理统计完成之后再继续执行;另一个方法就是使用互斥量。

下面给出用增加一个信号量的方法来解决该问题的代码,源文件名为semthread2.c,源代码如下:

#include <unistd.h> #include <pthread.h> #include <semaphore.h> #include <stdlib.h> #include <stdio.h> #include <string.h>   //线程函数 void *thread_func(void *msg); sem_t sem;//信号量 sem_t sem_add;//增加的信号量   #define MSG_SIZE 512   int main() {   int res = -1;   pthread_t thread;   void *thread_result = NULL;   char msg[MSG_SIZE];   //初始化信号量,初始值为0   res = sem_init(&sem, 0, 0);   if(res == -1)   {     perror("semaphore intitialization failed\n");     exit(EXIT_FAILURE);   }   //初始化信号量,初始值为1   res = sem_init(&sem_add, 0, 1);   if(res == -1)   {     perror("semaphore intitialization failed\n");     exit(EXIT_FAILURE);   }   //创建线程,并把msg作为线程函数的参数   res = pthread_create(&thread, NULL, thread_func, msg);   if(res != 0)   {     perror("pthread_create failed\n");     exit(EXIT_FAILURE);   }   //输入信息,以输入end结束,由于fgets会把回车(\n)也读入,所以判断时就变成了“end\n”   printf("Input some text. Enter 'end'to finish...\n");      sem_wait(&sem_add);   while(strcmp("end\n", msg) != 0)   {     if(strncmp("TEST", msg, 4) == 0)     {       strcpy(msg, "copy_data\n");       sem_post(&sem);       //把sem_add的值减1,即等待子线程处理完成       sem_wait(&sem_add);     }     fgets(msg, MSG_SIZE, stdin);     //把信号量加1     sem_post(&sem);     //把sem_add的值减1,即等待子线程处理完成     sem_wait(&sem_add);   }     printf("Waiting for thread to finish...\n");   //等待子线程结束   res = pthread_join(thread, &thread_result);   if(res != 0)   {     perror("pthread_join failed\n");     exit(EXIT_FAILURE);   }   printf("Thread joined\n");   //清理信号量   sem_destroy(&sem);   sem_destroy(&sem_add);   exit(EXIT_SUCCESS); }   void* thread_func(void *msg) {   char *ptr = msg;   //把信号量减1   sem_wait(&sem);   while(strcmp("end\n", msg) != 0)   {     int i = 0;     //把小写字母变成大写     for(; ptr[i] != '\0'; ++i)     {       if(ptr[i] >= 'a' && ptr[i] <= 'z')       {         ptr[i] -= 'a' - 'A';       }     }     printf("You input %d characters\n", i-1);     printf("To Uppercase: %s\n", ptr);     //把信号量加1,表明子线程处理完成     sem_post(&sem_add);     //把信号量减1     sem_wait(&sem);   }   sem_post(&sem_add);   //退出线程   pthread_exit(NULL); 

其运行结果如下:

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

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


  • 上一条:
    在阿里云Centos下如何安装Nginx
    下一条:
    CentOS SSH无密码登录的配置
  • 昵称:

    邮箱:

    0条评论 (评论内容有缓存机制,请悉知!)
    最新最热
    • 分类目录
    • 人生(杂谈)
    • 技术
    • linux
    • Java
    • php
    • 框架(架构)
    • 前端
    • ThinkPHP
    • 数据库
    • 微信(小程序)
    • Laravel
    • Redis
    • Docker
    • Go
    • swoole
    • Windows
    • Python
    • 苹果(mac/ios)
    • 相关文章
    • 在Linux系统中使用Iptables实现流量转发功能流程步骤(0个评论)
    • vim学习笔记-入门级需要了解的一些快捷键(0个评论)
    • 在centos7系统中实现分区并格式化挂载一块硬盘到/data目录流程步骤(0个评论)
    • 在Linux系统种查看某一个进程所占用的内存命令(0个评论)
    • Linux中grep命令中的10种高级用法浅析(0个评论)
    • 近期文章
    • 在go语言中使用api.geonames.org接口实现根据国际邮政编码获取地址信息功能(1个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf分页文件功能(0个评论)
    • gmail发邮件报错:534 5.7.9 Application-specific password required...解决方案(0个评论)
    • 欧盟关于强迫劳动的规定的官方举报渠道及官方举报网站(0个评论)
    • 在go语言中使用github.com/signintech/gopdf实现生成pdf文件功能(0个评论)
    • Laravel从Accel获得5700万美元A轮融资(0个评论)
    • 在go + gin中gorm实现指定搜索/区间搜索分页列表功能接口实例(0个评论)
    • 在go语言中实现IP/CIDR的ip和netmask互转及IP段形式互转及ip是否存在IP/CIDR(0个评论)
    • PHP 8.4 Alpha 1现已发布!(0个评论)
    • Laravel 11.15版本发布 - Eloquent Builder中添加的泛型(0个评论)
    • 近期评论
    • 122 在

      学历:一种延缓就业设计,生活需求下的权衡之选中评论 工作几年后,报名考研了,到现在还没认真学习备考,迷茫中。作为一名北漂互联网打工人..
    • 123 在

      Clash for Windows作者删库跑路了,github已404中评论 按理说只要你在国内,所有的流量进出都在监控范围内,不管你怎么隐藏也没用,想搞你分..
    • 原梓番博客 在

      在Laravel框架中使用模型Model分表最简单的方法中评论 好久好久都没看友情链接申请了,今天刚看,已经添加。..
    • 博主 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 @1111老铁这个不行了,可以看看近期评论的其他文章..
    • 1111 在

      佛跳墙vpn软件不会用?上不了网?佛跳墙vpn常见问题以及解决办法中评论 网站不能打开,博主百忙中能否发个APP下载链接,佛跳墙或极光..
    • 2016-11
    • 2017-07
    • 2017-10
    • 2017-11
    • 2018-01
    • 2018-02
    • 2020-03
    • 2020-04
    • 2020-05
    • 2020-06
    • 2021-02
    • 2021-03
    • 2021-04
    • 2021-06
    • 2021-07
    • 2021-08
    • 2021-09
    • 2021-10
    • 2021-11
    • 2021-12
    • 2022-01
    • 2022-03
    • 2022-04
    • 2022-08
    • 2022-11
    • 2022-12
    • 2023-01
    • 2023-02
    • 2023-03
    • 2023-06
    • 2023-07
    • 2023-10
    • 2023-12
    • 2024-01
    • 2024-04
    Top

    Copyright·© 2019 侯体宗版权所有· 粤ICP备20027696号 PHP交流群

    侯体宗的博客