Linux信号量(2)-POSIX 信号量
上一章,讲述了SYSTEM V信号量,主要运行于进程之间,本章主要介绍POSIX信号量:有名信号量、无名信号量。
POSIX信号量
POSIX信号量进程是3种 IPC(Inter-Process Communication) 机制之一,3种 IPC 机制源于 POSIX.1 的实时扩展。Single UNIX Specification 将3种机制(消息队列,信号量和共享存储)置于可选部分中。在 SUSv4 之前,POSIX 信号量接口已经被包含在信号量选项中。在 SUSv4 中,这些接口被移至了基本规范,而消息队列和共享存储接口依然是可选的。
POSIX 信号量接口意在解决 XSI 信号量接口的几个缺陷。
相比于 XSI 接口,POSIX 信号量接口考虑了更高性能的实现。
POSIX 信号量使用更简单:没有信号量集,在熟悉的文件系统操作后一些接口被模式化了。尽管没有要求一定要在文件系统中实现,但是一些系统的确是这么实现的。
POSIX 信号量在删除时表现更完美。回忆一下,当一个 XSI 信号量被删除时,使用这个信号量标识符的操作会失败,并将 errno 设置成 EIDRM。使用 POSIX 信号量时,操作能继续正常工作直到该信号量的最后一次引用被释放。
分类
POSIX信号量是一个sem_t类型的变量,但POSIX有两种信号量的实现机制:无名信号量和命名信号量。
无名信号量只可以在共享内存的情况下,比如实现进程中各个线程之间的互斥和同步,因此无名信号量也被称作基于内存的信号量;命名信号量通常用于不共享内存的情况下,比如进程间通信。
同时,在创建信号量时,根据信号量取值的不同,POSIX信号量还可以分为:
二值信号量:信号量的值只有0和1,这和互斥量很类似,若资源被锁住,信号量的值为0,若资源可用,则信号量的值为1;
计数信号量:信号量的值在0到一个大于1的限制值之间,该计数表示可用的资源的个数。
区别
有名信号量和无名信号量的差异在于创建和销毁的形式上,但是其他工作一样。
无名信号量只能存在于内存中,要求使用信号量的进程必须能访问信号量所在的这一块内存,所以无名信号量只能应用在同一进程内的线程之间(共享进程的内存),或者不同进程中已经映射相同内存内容到它们的地址空间中的线程(即信号量所在内存被通信的进程共享)。意思是说无名信号量只能通过共享内存访问。
相反,有名信号量可以通过名字访问,因此可以被任何知道它们名字的进程中的线程使用。
单个进程中使用 POSIX 信号量时,无名信号量更简单。
多个进程间使用 POSIX 信号量时,有名信号量更简单。
联系
无论是有名信号量还是无名信号量,都可以通过以下函数进行信号量值操作。
wait(P)
wait 为信号量值减一操作,总共有三个函数,函数原型如下:
#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);
Link with -pthread.这一句表示 gcc 编译时,要加 -pthread.
返回值:
若成功,返回 0 ;若出错,返回-1
sem_wait的作用是,若 sem 小于 0 ,则线程阻塞于信号量 sem ,直到 sem 大于 0 ;否则信号量值减1。
sem_trywait作用与sem_wait相同,只是此函数不阻塞线程,如果 sem 小于 0,直接返回一个错误(错误设置为 EAGAIN )。
sem_timedwait作用也与sem_wait相同,第二个参数表示阻塞时间,如果 sem 小于 0 ,则会阻塞,参数指定阻塞时间长度。abs_timeout 指向一个结构体,这个结构体由从 1970-01-01 00:00:00 +0000 (UTC) 开始的秒数和纳秒数构成。
结构体定义如下:
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds [0 .. 999999999] */
};
如果指定的阻塞时间到了,但是 sem 仍然小于 0 ,则会返回一个错误 (错误设置为 ETIMEDOUT )。
post(V)
post 为信号量值加一操作,函数原型如下:
#include <semaphore.h>
int sem_post(sem_t *sem);
Link with -pthread.
返回值:
若成功,返回 0 ;若出错,返回-1
无名信号量
接口函数
信号量的函数都以sem_开头,线程中使用的基本信号函数有4个,他们都声明在头文件semaphore.h中,该头文件定义了用于信号量操作的sem_t类型:
sem_init
该函数用于创建信号量,原型如下:
int sem_init(sem_t *sem, int pshared, unsigned int value);
功能:
该函数初始化由sem指向的信号对象,设置它的共享选项,并给它一个初始的整数值。pshared控制信号量的类型,如果其值为0,就表示信号量是当前进程的局部信号量,否则信号量就可以在多个进程间共享,value为sem的初始值。
返回值:
该函数调用成功返回0,失败返回-1。
sem_destroy
该函数用于对用完的信号量进行清理,其原型如下:
int sem_destroy(sem_t *sem);
返回值:
成功返回0,失败返回-1。
sem_getvalue函数
该函数返回当前信号量的值,通过restrict输出参数返回。如果当前信号量已经上锁(即同步对象不可用),那么返回值为0,或为负数,其绝对值就是等待该信号量解锁的线程数。
int sem_getvalue(sem_t *restrict, int *restrict);
使用实例
【实例1】:
#include <time.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <signal.h>
#include <semaphore.h>
sem_t sem;
#define handle_error(msg) do { \
perror(msg); \
exit(EXIT_FAILURE); \
}while (0)
static void handler(int sig){
write(STDOUT_FILENO, "sem_post() from handler\n", 24);
if(sem_post(&sem) == -1)
{
write(STDERR_FILENO, "sem_post() failed\n", 18);
_exit(EXIT_FAILURE);
}}
int main(int argc, char *argv[]){
int s;
struct timespec ts;
struct sigaction sa;
if (argc != 3)
{
fprintf(stderr, "Usage: %s <alarm-secs> <wait-secs>\n", argv[0]);
exit(EXIT_FAILURE);
}
if (sem_init(&sem, 0, 0) == -1)
handle_error("sem_init");
/* Establish SIGALRM handler; set alarm timer using argv[1] */
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if (sigaction(SIGALRM, &sa, NULL) == -1)
handle_error("sigaction");
alarm(atoi(argv[1]));
/* Calculate relative interval as current time plus
number of seconds given argv[2] */
if (clock_gettime(CLOCK_REALTIME, &ts) == -1)
handle_error("clock_gettime");
ts.tv_sec += atoi(argv[2]);
printf("main() about to call sem_timedwait()\n");
while ((s = sem_timedwait(&sem, &ts)) == -1 && errno == EINTR)
continue; /* Restart if interrupted by handler */
/* Check what happened */
if (s == -1)
{
if (errno == ETIMEDOUT)
printf("sem_timedwait() timed out\n");
else
perror("sem_timedwait");
}
else
{
printf("sem_timedwait() succeeded\n");
}
exit((s == 0) ? EXIT_SUCCESS : EXIT_FAILURE);
}
【实例2】:
#include <time.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <signal.h>
#include <semaphore.h>
sem_t sem;
void *func1(void *arg){
sem_wait(&sem);
int *running = (int *)arg;
printf("thread func1 running : %d\n", *running);
pthread_exit(NULL);
}
void *func2(void *arg)
{
printf("thread func2 running.\n");
sem_post(&sem);
pthread_exit(NULL);
}
int main(void)
{
int a = 3;
sem_init(&sem, 0, 0);
pthread_t thread_id[2];
pthread_create(&thread_id[0], NULL, func1, (void *)&a);
printf("main thread running.\n");
sleep(10);
pthread_create(&thread_id[1], NULL, func2, (void *)&a);
printf("main thread still running.\n");
pthread_join(thread_id[0], NULL);
pthread_join(thread_id[1], NULL);
sem_destroy(&sem);
return 0;
}
有名信号量
有时候也叫命名信号量,之所以称为命名信号量,是因为它有一个名字、一个用户ID、一个组ID和权限。这些是提供给不共享内存的那些进程使用命名信号量的接口。命名信号量的名字是一个遵守路径名构造规则的字符串。
接口函数
sem_open函数
该函数用于创建或打开一个命名信号量,其原型如下:
sem_t *sem_open(const char *name, int oflag);
sem_t *sem_open(const char *name, int oflag,mode_t mode, unsigned int value);
参数
name是一个标识信号量的字符串。
oflag用来确定是创建信号量还是连接已有的信号量。
oflag的参数可以为0,O_CREAT或O_EXCL:如果为0,表示打开一个已存在的信号量;如果为O_CREAT,表示如果信号量不存在就创建一个信号量,如果存在则打开被返回,此时mode和value都需要指定;如果为O_CREAT|O_EXCL,表示如果信号量存在则返回错误。mode
用于创建信号量时指定信号量的权限位,和open函数一样,包括:S_IRUSR、S_IWUSR、S_IRGRP、S_IWGRP、S_IROTH、S_IWOTH。value
表示创建信号量时,信号量的初始值。
sem_close函数
该函数用于关闭命名信号量:
int sem_close(sem_t *);
功能:
单个程序可以用sem_close函数关闭命名信号量,但是这样做并不能将信号量从系统中删除,因为命名信号量在单个程序执行之外是具有持久性的。当进程调用_exit、exit、exec或从main返回时,进程打开的命名信号量同样会被关闭。
sem_unlink函数
功能:
sem_unlink函数用于在所有进程关闭了命名信号量之后,将信号量从系统中删除:
int sem_unlink(const char *name);
信号量操作函数
与无名信号量一样。
使用实例
#include <time.h>
#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <signal.h>
#include <semaphore.h>
#define SEM_NAME " /sem_name"
sem_t *p_sem;
void *testThread(void *ptr){
sem_wait(p_sem);
sleep(2);
pthread_exit(NULL);}
int main(void){
int i = 0;
pthread_t pid;
int sem_val = 0;
p_sem = sem_open(SEM_NAME, O_CREAT, 0555, 5);
if(p_sem == NULL)
{
printf("sem_open %s failed!\n", SEM_NAME);
sem_unlink(SEM_NAME);
return -1;
}
for(i = 0; i < 7; i++)
{
pthread_create(&pid, NULL, testThread, NULL);
sleep(1);
// pthread_join(pid, NULL); // not needed, or loop
sem_getvalue(p_sem, &sem_val);
printf("semaphore value : %d\n", sem_val);
}
sem_close(p_sem);
sem_unlink(SEM_NAME);
return 0;
}
命名和无名信号量的持续性
命名信号量是随内核持续的。当命名信号量创建后,即使当前没有进程打开某个信号量,它的值依然保持,直到内核重新自举或调用sem_unlink()删除该信号量。
无名信号量的持续性要根据信号量在内存中的位置确定:
如果无名信号量是在单个进程内部的数据空间中,即信号量只能在进程内部的各个线程间共享,那么信号量是随进程的持续性,当进程终止时他也就消失了;
如果无名信号量位于不同进程的共享内存区,因此只要该共享内存区仍然存在,该信号量就会一直存在;所以此时无名信号量是随内核的持续性。
信号量-互斥量-条件变量
很多时候信号量、互斥量和条件变量都可以在某种应用中使用,那这三者的差异有哪些呢?下面列出了这三者之间的差异:
互斥量必须由给它上锁的线程解锁;而信号量不需要由等待它的线程进行挂出,可以在其他进程进行挂出操作;
互斥量要么被锁住,要么被解开,只有这两种状态;而信号量的值可以支持多个进程/线程成功的进行wait操作;
信号量的挂出操作总是被记住,因为信号量有一个计数值,挂出操作总会将该计数值加1,然而当条件变量发送一个信号时,如果没有线程等待在条件变量,那么该信号就会丢失。
本文使用 mdnice 排版
Linux信号量(2)-POSIX 信号量的更多相关文章
- Linux进程同步之POSIX信号量
POSIX信号量是属于POSIX标准系统接口定义的实时扩展部分.在SUS(Single UNIX Specification)单一规范中,定义的XSI IPC中也同样定义了人们通常称为System V ...
- linux网络编程-posix信号量与互斥锁(39)
-posix信号量信号量 是打开一个有名的信号量 sem_init是打开一个无名的信号量,无名信号量的销毁用sem_destroy sem_wait和sem_post是对信号量进行pv操作,既可以使用 ...
- system V信号量和Posix信号量
一.函数上的区别 信号量有两种实现:传统的System V信号量和新的POSIX信号量.它们所提供的函数很容易被区分:对于所有System V信号量函数,在它们的名字里面没有下划线.例如,应该是sem ...
- linux POSIX 信号量介绍
信号量一.什么是信号量信号量的使用主要是用来保护共享资源,使得资源在一个时刻只有一个进程(线程)使用.多线程可以同时运行多个线程函数完成功能,但是对于共享数据如果不加以锁定,随意改变共享数据的值会发生 ...
- linux Posix 信号量 二
一.Posix信号量 1.Posix信号量分为两种: 1. 有名信号量:使用Posix IPC名字标识(有名信号量总是既可用于线程间的同步,又可以用于进程间的同步) 2. 内存信号量:存放在共 ...
- 多线程编程之Apue3rd_Chapter15.10之posix信号量
看了APUE的chapter15,只重点看了15.10,学习了posix信号量.Posix信号量比起xsi信号量的优点是性能更好,在Linux3.2.0平台上性能提升很大.其中命名信号量使用方法如下. ...
- Linux进程间通信IPC学习笔记之同步二(Posix 信号量)
Linux进程间通信IPC学习笔记之同步二(Posix 信号量)
- linux Posix 信号量 一
信号量是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语. linux提供两种信号量,“内核信号量”和“用户态进程信号量”,“用户态信号量”又分为“Posix”,“System V”信号 ...
- Linux IPC POSIX 信号量
模型 #include<semaphore.h> #include<sys/stat.h> #include<fcntl.h> sem_open() //初始化并打 ...
- Linux多线程实践(5) --Posix信号量与互斥量解决生产者消费者问题
Posix信号量 Posix 信号量 有名信号量 无名信号量 sem_open sem_init sem_close sem_destroy sem_unlink sem_wait sem_post ...
随机推荐
- P2045 方格取数加强版题解
题目链接:P2045 方格取数加强版 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目: 出一个 n*n 的矩阵,每一格有一个非负整数 A{i,j}且A{i,j} <=10 ...
- AI Agent实战:智能检索在Kingbase数据库管理中的优势应用
前言 在信息技术飞速发展的今天,数据库管理已成为IT专业人员日常工作中不可或缺的一部分.然而,面对复杂的SQL问题,传统的web搜索往往难以提供精准的答案,尤其是在针对特定数据库系统,如金仓数据库时, ...
- SpringBoot实现RequestBodyAdvice和ResponseBodyAdvice接口
Spring Boot 提供了一种机制,允许开发者在请求体(RequestBody)和响应体(ResponseBody)被处理之前和之后执行自定义逻辑.这通过 RequestBodyAdvice 和 ...
- 通过vscode写博客
通过Vscode写博客到博客园 前言 在以前的写作方式都是通过博客园内置的markdown进行工作,但是在实际使用过程中,感觉不是很方便,所以找到了用VSCode插件写作的方法. 所需插件 博客园Cn ...
- Nunjucks
Nunjucks是什么东东?其实它是一个模板引擎. 那什么是模板引擎? 模板引擎就是基于模板配合数据构造出字符串输出的一个组件.比如下面的函数就是一个模板引擎: function examResult ...
- SqlParameter,参数化查询问题
SqlParameter p = new SqlParameter("@pageIndex", (object)pageIndex); SqlParameter带有两个参数的构造函 ...
- oeasy教您玩转vim - 25 - 更多颜色
更多颜色 回忆上节课内容 我们上次深入了配色方案 定义了自己的配色方案 oeasy 建立了自己的配色 oeasy 在状态栏应用了自己的配色 明确能用的颜色 先胡乱地尝试一下修改颜色代码 hi ...
- SQL Server 帐号权限管理及C#编程应用(图解)
昨晚在群里讲解这部分内容,因为好久没操作过了,差点翻车...今天把它整理一下发出来,方便没听明白的小伙伴学习和理解. 我们平时学习数据库时,要么使用sa帐号,要么用windows默认帐号登录,总之都拥 ...
- BeanUtils.copyProperties无法复制list对象,替换为lambda表达式
List<Setmeal> setmeals = setmealMapper.selectList(queryWrapper); List<SetmealVO>vo=new A ...
- 【PhpAdmin】小皮面板配置PhpAdmin
1.先用面板开启MySQL和Apache服务 2.MySQL的端口不在3306,我改为3307 3.新建一个站点,声明域名和端口配置 没有配置本地域名映射,这个域名就没有什么用,还是依靠localho ...