信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。
背景
数据竞争
作用:
控制多进程共享资源的访问(资源有限并且不共享)
本质
任一时刻只能有一个进程访问临界区(代码),数据更新的代码。
基本操作:PV
原子操作操作也被成为PV原语(P来源于Dutchproberen"测试",V来源于Dutchverhogen"增加")
信号
分类 取值
P(信号量) 0:挂起进程;>0:减1
V(信号量) 0:恢复进程;>0:加1
POSIX信号量
查看:man sem_overview
1 接口
头文件:semaphore.h
库:pthread
分类
信号量分为命名信号量(基于文件)与匿名信号量(基于内存)两种。
5.2 命名信号量/基于文件
操作 函数
创建 sem_t *sem_open(const char *name, int oflag, mode_t mode,unsigned int value)
删除 int sem_unlink(const char *name)
打开 sem_t *sem_open(const char *name, int oflag)
关闭 int sem_close(sem_t *sem)
挂出 int sem_post(sem_t *sem)
等待 int sem_wait(sem_t *sem)
尝试等待 int sem_trywait(sem_t *sem)
获取信号量的值 int sem_getvalue(sem_t *sem, int *sval)
5.2.1创建
sem_t *sem_open(const char *name, int oflag, mode_t mode,unsigned int value)
1
参数
参数 含义
name 信号量IPC名字
oflag 标志
mode 权限位
value 信号量初始值
返回值
返回值 含义
非SEM_FAILED 信号量的指针
SEM_FAILED 出错
5.2.2 删除
int sem_unlink(const char *name)
1
参数:
name: 信号量IPC名字
返回值
返回值 含义
-1 出错
0 成功
5.2.3 打开
sem_t *sem_open(const char *name, int oflag)
1
参数:
参数 含义
name 信号量IPC名字
oflag 标志
返回值
返回值 含义
非SEM_FAILED 信号量的指针
SEM_FAILED 出错
5.2.4 关闭
int sem_close(sem_t *sem)
1
参数:
sem:信号量的指针
返回值
返回值 含义
-1 出错
0 成功
5.2.5 挂出
int sem_post(sem_t *sem)
1
参数:
sem:信号量的指针
返回值
返回值 含义
-1 出错
0 成功
5.2.6 等待
int sem_wait(sem_t *sem)
1
参数:
sem:信号量的指针
返回值
返回值 含义
-1 出错
0 成功
5.2.7 尝试等待
int sem_trywait(sem_t *sem)
1
参数:
sem:信号量的指针
返回值
返回值 含义
-1 出错
0 成功
5.2.8 获取信号量的值
int sem_getvalue(sem_t *sem, int *sval)
1
参数:
参数 含义
sem 信号量的指针
sval 信号量的值
返回值
返回值 含义
-1 出错
0 成功
5.3 匿名信号量/基于内存
操作 函数
初始化 int sem_init (sem_t *sem , int pshared, unsigned int value)
销毁 int sem_destroy(sem_t *sem)
挂出 int sem_post(sem_t *sem)
等待 int sem_wait(sem_t *sem)
尝试等待 int sem_trywait(sem_t *sem)
获取信号量的值 int sem_getvalue(sem_t *sem, int *sval)
注:其中挂出和等待操作与命名信号量相同。
5.3.1 初始化
int sem_init (sem_t *sem , int pshared, unsigned int value)
1
参数
参数 含义
sem 信号量的指针
pshared 共享方式。0:线程间共享;1:进程间共享,需要共享内存
value 信号量初始值
返回值
返回值 含义
-1 出错
0 成功
5.3.2 销毁
int sem_destroy(sem_t *sem)
1
参数:
sem:信号量的指针
返回值
返回值 含义
-1 出错
0 成功
5.4 命名信号量 vs. 匿名信号量
功能区别:
1、命名信号量一般是用在进程间同步,匿名信号量一般用在线程间同步。
2、命名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。
3、匿名信号量,其值保存在内存中。
代码区别:
主要在于两种信号量初始化和销毁的方式不同。
2 实例
2.1 实例1:父子进程出现数据竞争
#include <iostream>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <cstdio>
#include <fcntl.h>
using namespace std;
int main(){
int* p = (int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
if(MAP_FAILED == 0){
perror("mmap error");
return 1;
}
*p = 0;
if(fork()){
for(int i = 0;i < 10000;++i){
cout << "parent:" << p << ":" << ++*p << endl;
}
wait(NULL);
}else{
for(int i = 0;i < 10000;++i){
cout << "child:" << p << ":" << --*p << endl;
}
}
munmap(p,sizeof(int));
p = NULL;
}
数据竞争导致父子进程访问同一片内存时出错。
parent:0x7f933cc16000:3880
parent:0x7f933cc16000:3881
parent:0x7f933cc16000:3882
parent:0x7f933cc16000:3883
parent:0x7f933cc16000:3884
child:0x7f933cc16000:3723
child:0x7f933cc16000:3884
child:0x7f933cc16000:3883
child:0x7f933cc16000:3882
解决方案:
(1)加入命名信号量,同一时间只允许单个进程进行读写操作。
#include <iostream>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <cstdio>
#include <fcntl.h>
using namespace std;
int main(){
int* p = (int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
if(MAP_FAILED == 0){
perror("mmap error");
return 1;
}
*p = 0;
if(fork()){
for(int i = 0;i < 10000;++i){
cout << "parent:" << p << ":" << ++*p << endl;
}
wait(NULL);
}else{
for(int i = 0;i < 10000;++i){
cout << "child:" << p << ":" << --*p << endl;
}
}
munmap(p,sizeof(int));
p = NULL;
}
(2)匿名信号量的方式。
#include <iostream>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/wait.h>
#include <cstdio>
#include <fcntl.h>
#include <semaphore.h>
#include <sstream>
using namespace std;
int main() {
sem_t* psem = (sem_t*)mmap(NULL,sizeof(sem_t),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
sem_init(psem,1,1);//初始化,前面的1表示进程之间共享,后面1表示值
if(SEM_FAILED == psem) {
perror("sem_open error");
return 1;
}
int* p = (int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
if(MAP_FAILED == p) {
perror("mmap error");
return 1;
}
*p = 0;
fork();
for(int i = 0; i < 2000; ++i) {
sem_wait(psem);
ostringstream oss;
oss << getpid() << " :"<< --*p << endl;
string s = oss.str();
write(STDOUT_FILENO,s.c_str(),s.size()+1);
sem_post(psem);
}
munmap(p,sizeof(int));
sem_destroy(psem);
psem = NULL;
p = NULL;
}
注:
- 编译时加-lrt -pthread
- 父子进程之间可以用匿名方式,不同进程之间只能用命名方式。
5.5.2 实例2:加入信号量的read.cpp
#include <iostream>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/shm.h>
#include <fcntl.h>
#include <semaphore.h>
using namespace std;
int main(int argc,char* argv[]){
if(2 != argc){
cout << "arguement error" << endl;
cout << "Usage: " << argv[0] << "shm file" << endl;
return 1;
}
int fd = shm_open(argv[1],O_RDWR|O_CREAT,0666);
if(-1 == fd){
perror("open error");
return 1;
}
sem_t* psem = sem_open("/sem.test",O_RDWR,0666,1);
int* p = (int*)mmap(NULL,sizeof(int),PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
if(MAP_FAILED == p){
perror("map error");
return 1;
}
for(int i = 0;i < 100;++i){
sem_wait(psem);
cout << getpid() << "read:" << --*p << endl;
sem_post(psem);
}
munmap(p,sizeof(int));
p = NULL;
sem_close(psem);
psem = NULL;
}
————————————————
版权声明:本文为CSDN博主「_深蓝.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42488216/article/details/124969154