信号量的使用

简介: 信号量的使用

信号量本身代表一种资源,其本质是一个非负的整数计数器,被用来控制对公共资源的访问。换句话说,信号量的核心内容是信号量的。其工作原理是:所有对共享资源操作的线程,在访问共享资源之前,都需要先操作信号量的值。操作信号量的值又可以称为 PV 操作, P 操作为申请信号量V 操作为释放信号量。当申请信号量成功时,信号量的值减1,而释放信号量成功时,信号量的值加1。但是当信号量的值为 0 时,申请信号量时将会阻塞其值不能减为负数。利用这一特性,即可实现对共享资源访问的控制。


信号量作为一种同步互斥机制,若用于实现互斥时,多线程只需设置一个信号量。若用于实现同步时,则需要设置多个信号量,并通过设置不同的信号量的初始值来实现线程的执行顺序。


本笔记将介绍基于 POSIX无名信号量,其信号量的操作与互斥锁类似。

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);点击复制复制失败已复制


sem_init() 函数被用来进行信号量的初始化。参数 sem 表示信号量的标识符。

pshared 参数用来设置信号量的使用环境,其值为 0 ,表示信号量用于同一个进程的多个线程之间使用;其值为非 0 ,表示信号量用于进程间使用。 value 为重要的参数,表示信号量的初始值。

#include <semaphore.h>
int sem_destroy(sem_t *sem);点击复制复制失败已复制


sem_destroy() 函数被用来摧毁信号量,参数 sem 表示信号量的标志符。

#include <semaphore.h>
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);点击复制复制失败已复制


sem_wait() 函数用来执行申请信号量的操作,当申请信号量成功时,信号量的值减1,当信号量的值为 0 时,此操作将会阻塞,直到其他线程执行释放信号量。

sem_trywait() 函数与 sem_wait() 函数类似,唯一的区别在于 sem_trywait() 函数不会阻塞,当信号量为 0 时,函数直接返回错误码EAGAIN

sem_timedwait() 函数同样,多了参数 abs_timeout ,用来设置时间限制,如果在该时间内,信号量仍然不能申请,那么该函数不会一直阻塞,而是返回错误码 ETIMEOUT


#include <semaphore.h>
int sem_post(sem_t *sem);点击复制复制失败已复制


sem_post() 函数用来执行释放信号量的操作,当释放信号量成功时,信号量的值

1

#include <semaphore.h>
int sem_getvalue(sem_t *sem, int *sval);点击复制复制失败已复制


sem_getvalue() 函数用于获得当前信号量的值,并保存在参数 sval 中。


信号量可以应用的场合很多,下例展示其使用方法:

#include <pthread.h>
#include <stdio.h>
#include <semaphore.h>
#include <string.h>
#define N 32
#define errlog(errmsg)                                                         \
  do {                                                                         \
    perror(errmsg);                                                            \
    printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__);              \
    return -1;                                                                 \
  } while (0)
char buf[N] = "";
void *thread1_handler(void *arg) {
  while (1) {
    fgets(buf,N,stdin);
    buf[strlen(buf)-1] = '\0';
  }
  pthread_exit("thread1...exit");
}
void *thread2_handler(void *arg) {
  while (1) {
    printf("buf:%s\n",buf);
    sleep(1);
  }
  pthread_exit("thread2...exit");
}
int main(int argc, const char *argv[]) {
  pthread_t thread1, thread2;
  if (pthread_create(&thread1, NULL, thread1_handler, NULL) != 0) {
    errlog("pthread_create1 error");
  }
  if (pthread_create(&thread2, NULL, thread2_handler, NULL) != 0) {
    errlog("pthread_create2 error");
  }
  pthread_join(thread1, NULL);
  pthread_join(thread2, NULL);
  return 0;
}点击复制复制失败已复制


编译并运行,结果如下:

$ gcc main.c && ./a.out
buf:
buf:
buf:
buf:sssdfsdfad
buf:sssdfsdfad
buf:sssdfsdfad
buf:sssdfsdfad点击复制复制失败已复制


可以看出,线程 1fgets() 是一个阻塞函数,比较特殊。但是线程 2 在共享数据并不确定的情况下,轮询读取共享的数组,当终端输入内容后(线程1读取输入内容),线程 2 则读取输入的内容并将内容一直打印输出,这样的结果并不是程序的本意。


程序设计的目的是让线程 1 可以写数据,线程 2 读数据,并保证数据的实时、有效,因此在这里可以选择引入两个信号量,实现同步的操作,使线程可以按照一定的顺序实现写入与读取,如下所示:

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <string.h>
#define N 32
#define errlog(errmsg)                                                         \
  do {                                                                         \
    perror(errmsg);                                                            \
    printf("--%s--%s--%d--\n", __FILE__, __FUNCTION__, __LINE__);              \
    return -1;                                                                 \
  } while (0)
char buf[N] = "";
sem_t sem1, sem2;
void *thread1_handler(void *arg) {
  while (1) {
    sem_wait(&sem2);
    fgets(buf, N, stdin);
    buf[strlen(buf) - 1] = '\0';
    sem_post(&sem1);
  }
  pthread_exit("thread1...exit");
}
void *thread2_handler(void *arg) {
  while (1) {
    sem_wait(&sem1);
    printf("buf:%s\n", buf);
    sleep(1);
    sem_post(&sem2);
  }
  pthread_exit("thread2...exit");
}
int main(int argc, const char *argv[]) {
  pthread_t thread1, thread2;
  if (sem_init(&sem1, 0, 0) < 0) {
    errlog("sem_init error");
  }
  if (sem_init(&sem2, 0, 1) < 0) {
    errlog("sem_init error");
  }
  if (pthread_create(&thread1, NULL, thread1_handler, NULL) != 0) {
    errlog("pthread_create1 error");
  }
  if (pthread_create(&thread2, NULL, thread2_handler, NULL) != 0) {
    errlog("pthread_create2 error");
  }
  pthread_join(thread1, NULL);
  pthread_join(thread2, NULL);
  sem_destroy(&sem1);
  sem_destroy(&sem2);
  return 0;
}点击复制复制失败已复制


编译并运行,结果如下:

$ gcc main.c && ./a.out
hello
buf:hello
world
buf:world点击复制复制失败已复制


线程 2 一开始就不会直接输出,而是阻塞等待;当线程 1 对共享资源写操作完成之后,可以进行读操作并输入,之后可以继续写入并读取,后续维持此状态。

目录
相关文章
|
7月前
|
程序员
信号量和管程
信号量和管程
39 0
|
安全
理解信号量
理解信号量
|
7月前
|
前端开发 安全 C++
c++11线程、互斥量、条件变量等
c++11线程、互斥量、条件变量等
|
7月前
多线程并发之Semaphore(信号量)使用详解
多线程并发之Semaphore(信号量)使用详解
2543 0
|
存储
信号量(下)
信号量(下)
44 0
|
算法
信号量(上)
信号量(上)
39 0
|
机器学习/深度学习 C语言
信号量
信号量
92 0
|
存储 网络协议 API
4-FreeRTOS队列、互斥、信号量
4-FreeRTOS队列、互斥、信号量
Semaphore信号量
Semaphore 可以用来限制或管理数量有限资源的使用情况 - 信号量的作用是用来维护一个“许可证”,的计数,线程可以获取 许可证,那信号量剩余许可证就减一,线程也可以是否一个许可证,那剩余的许可证就加一,当信号量拥有的许可证为0时,那么下一个线程想获得许可证,就要进行等待,直到另外线程释放许可证
284 0
Semaphore信号量
信号量Semaphore的使用
允许多个线程同时访问:信号量(Semaphore) 信号量为多线程协作提供了更为强大的控制方法。广义上说,信号量是对锁的扩展。无论是内部锁synchronized还是重入锁ReentrantLock,一次都只允许一个线程访问一个资源,而信号量指定多个线程访问同一个资源。
1338 0