音效素材网提供各类素材,打造精品素材网站!

站内导航 站长工具 投稿中心 手机访问

音效素材

详解Linux多线程使用信号量同步
日期:2016-10-22 15:21:35   来源:脚本之家

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

一、什么是信号量

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

而只有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时,可能运行不正常,子线程不能正常退出,从而导致程序不能退出。

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

    您感兴趣的教程

    在docker中安装mysql详解

    本篇文章主要介绍了在docker中安装mysql详解,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编...

    详解 安装 docker mysql

    win10中文输入法仅在桌面显示怎么办?

    win10中文输入法仅在桌面显示怎么办?

    win10系统使用搜狗,QQ输入法只有在显示桌面的时候才出来,在使用其他程序输入框里面却只能输入字母数字,win10中...

    win10 中文输入法

    一分钟掌握linux系统目录结构

    这篇文章主要介绍了linux系统目录结构,通过结构图和多张表格了解linux系统目录结构,感兴趣的小伙伴们可以参考一...

    结构 目录 系统 linux

    PHP程序员玩转Linux系列 Linux和Windows安装

    这篇文章主要为大家详细介绍了PHP程序员玩转Linux系列文章,Linux和Windows安装nginx教程,具有一定的参考价值,感兴趣...

    玩转 程序员 安装 系列 PHP

    win10怎么安装杜比音效Doby V4.1 win10安装杜

    第四代杜比®家庭影院®技术包含了一整套协同工作的技术,让PC 发出清晰的环绕声同时第四代杜比家庭影院技术...

    win10杜比音效

    纯CSS实现iOS风格打开关闭选择框功能

    这篇文章主要介绍了纯CSS实现iOS风格打开关闭选择框,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作...

    css ios c

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的办法

    Win7如何给C盘扩容 Win7系统电脑C盘扩容的

    Win7给电脑C盘扩容的办法大家知道吗?当系统分区C盘空间不足时,就需要给它扩容了,如果不管,C盘没有足够的空间...

    Win7 C盘 扩容

    百度推广竞品词的投放策略

    SEM是基于关键词搜索的营销活动。作为推广人员,我们所做的工作,就是打理成千上万的关键词,关注它们的质量度...

    百度推广 竞品词

    Visual Studio Code(vscode) git的使用教程

    这篇文章主要介绍了详解Visual Studio Code(vscode) git的使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...

    教程 Studio Visual Code git

    七牛云储存创始人分享七牛的创立故事与

    这篇文章主要介绍了七牛云储存创始人分享七牛的创立故事与对Go语言的应用,七牛选用Go语言这门新兴的编程语言进行...

    七牛 Go语言

    Win10预览版Mobile 10547即将发布 9月19日上午

    微软副总裁Gabriel Aul的Twitter透露了 Win10 Mobile预览版10536即将发布,他表示该版本已进入内部慢速版阶段,发布时间目...

    Win10 预览版

    HTML标签meta总结,HTML5 head meta 属性整理

    移动前端开发中添加一些webkit专属的HTML5头部标签,帮助浏览器更好解析HTML代码,更好地将移动web前端页面表现出来...

    移动端html5模拟长按事件的实现方法

    这篇文章主要介绍了移动端html5模拟长按事件的实现方法的相关资料,小编觉得挺不错的,现在分享给大家,也给大家...

    移动端 html5 长按

    HTML常用meta大全(推荐)

    这篇文章主要介绍了HTML常用meta大全(推荐),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参...

    cdr怎么把图片转换成位图? cdr图片转换为位图的教程

    cdr怎么把图片转换成位图? cdr图片转换为

    cdr怎么把图片转换成位图?cdr中插入的图片想要转换成位图,该怎么转换呢?下面我们就来看看cdr图片转换为位图的...

    cdr 图片 位图

    win10系统怎么录屏?win10系统自带录屏详细教程

    win10系统怎么录屏?win10系统自带录屏详细

    当我们是使用win10系统的时候,想要录制电脑上的画面,这时候有人会想到下个第三方软件,其实可以用电脑上的自带...

    win10 系统自带录屏 详细教程

    + 更多教程 +
    WIN服务器linux服务器FTP服务器DNS服务器其他