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

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

一、什么是信号量

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

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

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

以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索linux
, 多线程
, 信号量
同步信号量
linux多线程信号量、linux信号量详解、linux 同步信号量、linux 多线程同步、linux多线程同步机制,以便于您获取更多的相关知识。

时间: 2024-09-19 12:04:59

详解Linux多线程使用信号量同步_Linux的相关文章

详解Linux多线程编程(不限Linux)_Linux

前言 线程?为什么有了进程还需要线程呢,他们有什么区别?使用线程有什么优势呢?还有多线程编程的一些细节问题,如线程之间怎样同步.互斥,这些东西将在本文中介绍.我在某QQ群里见到这样一道面试题: 是否熟悉POSIX多线程编程技术?如熟悉,编写程序完成如下功能: 1)有一int型全局变量g_Flag初始值为0: 2) 在主线称中起动线程1,打印"this is thread1",并将g_Flag设置为1 3) 在主线称中启动线程2,打印"this is thread2"

实例详解Linux下的Make命令_Linux

前言 无论是在linux 还是在Unix环境 中,make都是一个非常重要的编译命令.不管是自己进行项目开发还是安装应用软件,我们都经常要用到make或make install.利用make工具,我们可以将大型的开发项目分解成为多个更易于管理的模块,对于一个包括几百个源文件的应用程序,使用make和 makefile工具就可以简洁明快地理顺各个源文件之间纷繁复杂的相互关系.而且如此多的源文件,如果每次都要键入gcc命令进行编译的话,那对程序员 来说简直就是一场灾难.而make工具则可自动完成编译

图文详解Linux服务器搭建JDK环境_Linux

首先,当然是去下载Linux的JDK咯. 先看你Linux的系统多少位: getconf LONG_BIT 然后去下载对应的JDK位数 版本.-自己去谷歌搜索哦 我这里下载的是:jdk-7u79-linux-x64.tar.gz 首先我创建了2个文件夹: mkdir -p /java/jdk jdk-7u79-linux-x64.tar.gz我放在java目录下 java -version #查看服务器是否安装过jdk 我没有安装过,会提示 -bash: java: command not fo

详解iOS 多线程 锁 互斥 同步_IOS

在iOS中有几种方法来解决多线程访问同一个内存地址的互斥同步问题: 方法一,@synchronized(id anObject),(最简单的方法) 会自动对参数对象加锁,保证临界区内的代码线程安全 @synchronized(self) { // 这段代码对其他 @synchronized(self) 都是互斥的 // self 指向同一个对象 } 方法二,NSLock NSLock对象实现了NSLocking protocol,包含几个方法: lock,加锁 unlock,解锁 tryLock

详解Linux中vi命令大全_linux shell

vi编辑器是所有Unix及Linux系统下标准的编辑器,它的强大不逊色于任何最新的文本编辑器,这里只是简单地介绍一下它的用法和一小部分指令.由于 对Unix及Linux系统的任何版本,vi编辑器是完全相同的,因此您可以在其他任何介绍vi的地方进一步了解它.Vi也是Linux中最基本的文本编 辑器,学会它后,您将在Linux的世界里畅行无阻. vi的基本概念 基本上vi可以分为三种状态,分别是命令模式(command mode).插入模式(Insert mode)和底行模式(last line m

详解linux软连接和硬链接_Linux

本篇文章详细介绍了linux软连接和硬链接,废话不多说,接着往下看把. 一 链接文件 Linux链接分两种,一种被称为硬链接(Hard Link),另一种被称为符号链接(Symbolic Link).默认情况下,ln命令产生硬链接. [软连接] 另外一种连接称之为符号连接(Symbolic Link),也叫软连接.软链接文件有类似于Windows的快捷方式.它实际上是一个特殊的文件.在符号连接中,文件实际上是一个文本文件,其中包含的有另一文件的位置信息. 链接文件甚至可以链接不存在的文件,这就产

详解Java编程中线程同步以及定时启动线程的方法_java

使用wait()与notify()实现线程间协作 1. wait()与notify()/notifyAll()调用sleep()和yield()的时候锁并没有被释放,而调用wait()将释放锁.这样另一个任务(线程)可以获得当前对象的锁,从而进入它的synchronized方法中.可以通过notify()/notifyAll(),或者时间到期,从wait()中恢复执行. 只能在同步控制方法或同步块中调用wait().notify()和notifyAll().如果在非同步的方法里调用这些方法,在运

详解Java多线程编程中的线程同步方法_java

1.多线程的同步: 1.1.同步机制:在多线程中,可能有多个线程试图访问一个有限的资源,必须预防这种情况的发生.所以引入了同步机制:在线程使用一个资源时为其加锁,这样其他的线程便不能访问那个资源了,直到解锁后才可以访问. 1.2.共享成员变量的例子:成员变量与局部变量: 成员变量: 如果一个变量是成员变量,那么多个线程对同一个对象的成员变量进行操作,这多个线程是共享一个成员变量的. 局部变量: 如果一个变量是局部变量,那么多个线程对同一个对象进行操作,每个线程都会有一个该局部变量的拷贝.他们之间

详解iOS多线程GCD问题_IOS

在iOS所有实现多线程的方案中,GCD应该是最有魅力的,因为GCD本身是苹果公司为多核的并行运算提出的解决方案.GCD在工作时会自动利用更多的处理器核心,以充分利用更强大的机器.GCD是Grand Central Dispatch的简称,它是基于C语言的.如果使用GCD,完全由系统管理线程,我们不需要编写线程代码.只需定义想要执行的任务,然后添加到适当的调度队列(dispatch queue).GCD会负责创建线程和调度你的任务,系统直接提供线程管理 dispatch queue分成以下三种: