0. 信号量
#include
#include
#include
int main(int argc,char * argv[]){
fork();
int i=0;
for(;i<5;i++){
printf("PID:%d,enter\n",getpid());
sleep(1);// do something
printf("PID:%d,do something\n",getpid());
printf("PID:%d,leave\n",getpid());
}
}
数据竞争(Data Race)
根据共享资源的数目可分为二值信号量和计数信号量两类。
No. |
分类 |
取值 |
e.g. |
1 |
二值信号量 |
0 和1 |
指示锁 |
2 |
计数信号量 |
0 和n |
停车场电子牌 |
原子操作操作也被成为PV原语(P来源于Dutchproberen"测试",V来源于Dutchverhogen"增加")
No. |
分类 |
取值 |
1 |
P(信号量) |
0 :挂起进程;>0 :减1 |
2 |
V(信号量) |
0 :恢复进程;>0 :加1 |
1. POSIX 信号量
- 资料:
unpv22e-ch10.1~10.13
- 查看:
man sem_overview
1.1 接口
1.2 函数
1.2.1 命名信号量/基于文件
No. |
操作 |
函数 |
1 |
创建 |
sem_t *sem_open(const char *name, int oflag, mode_t mode,unsigned int value) |
2 |
删除 |
int sem_unlink(const char *name) |
3 |
打开 |
sem_t *sem_open(const char *name, int oflag) |
4 |
关闭 |
int sem_close(sem_t *sem) |
5 |
挂出 |
int sem_post(sem_t *sem) |
6 |
等待 |
int sem_wait(sem_t *sem) |
7 |
尝试等待 |
int sem_trywait(sem_t *sem) |
8 |
获取信号量的值 |
int sem_getvalue(sem_t *sem, int *sval) |
1.2.1.1 创建
sem_t *sem_open(const char *name, int oflag, mode_t mode,unsigned int value)
No. |
参数 |
含义 |
1 |
name |
信号量IPC名字 |
2 |
oflag |
标志 |
3 |
mode |
权限位 |
4 |
value |
信号量初始值 |
IPC名字有什么要求?
No. |
返回值 |
含义 |
1 |
非SEM_FAILED |
信号量的指针 |
2 |
SEM_FAILED |
出错 |
1.2.1.2 删除
int sem_unlink(const char *name)
No. |
参数 |
含义 |
1 |
name |
信号量IPC名字 |
No. |
返回值 |
含义 |
1 |
-1 |
出错 |
2 |
0 |
成功 |
1.2.1.3 打开
sem_t *sem_open(const char *name, int oflag)
No. |
参数 |
含义 |
1 |
name |
信号量IPC名字 |
2 |
oflag |
标志 |
No. |
返回值 |
含义 |
1 |
非SEM_FAILED |
信号量的指针 |
2 |
SEM_FAILED |
出错 |
1.2.1.4 关闭
int sem_close(sem_t *sem)
No. |
返回值 |
含义 |
1 |
-1 |
出错 |
2 |
0 |
成功 |
1.2.1.5 挂出
int sem_post(sem_t *sem)
No. |
返回值 |
含义 |
1 |
-1 |
出错 |
2 |
0 |
成功 |
1.2.1.6 等待
int sem_wait(sem_t *sem)
No. |
返回值 |
含义 |
1 |
-1 |
出错 |
2 |
0 |
成功 |
1.2.1.7 尝试等待
int sem_trywait(sem_t *sem)
No. |
返回值 |
含义 |
1 |
-1 |
出错 |
2 |
0 |
成功 |
1.2.1.8 获取信号量的值
int sem_getvalue(sem_t *sem, int *sval)
No. |
参数 |
含义 |
1 |
sem |
信号量的指针 |
2 |
sval |
信号量的值 |
No. |
返回值 |
含义 |
1 |
-1 |
出错 |
2 |
0 |
成功 |
数据竞争解决方法
#include
#include
#include
#include
#include
int main(int argc,char * argv[]){
sem_t* sem = sem_open("/sem.tmp",O_CREAT|O_RDWR,0644,1);
fork();
int i=0;
for(;i<5;i++){
sleep(1);
sem_wait(sem);
printf("PID:%d,enter\n",getpid());
printf("PID:%d,do something\n",getpid());
printf("PID:%d,leave\n",getpid());
sem_post(sem);
}
}
#include
#include
#include
#include
#include
using namespace std;
enum ARGS{
CMD,SEM_NAME,SHM_NAME
};
int main(int argc,char* argv[]){
if(3!=argc){
cout << "Usage:" << argv[CMD] << " sem_name shm_name" << endl;
return EXIT_FAILURE;
}
// 创建(如果不存在)打开共享内存
int shm = shm_open(argv[SHM_NAME],O_CREAT|O_RDWR,0644);
if(-1 == shm){
perror("shm_open error");
return EXIT_FAILURE;
}
ftruncate(shm,sizeof(int));
// 建立映射
void* p = mmap(NULL,sizeof(int),PROT_WRITE|PROT_READ,MAP_SHARED,shm,0);
if(MAP_FAILED == p){
perror("mmap error");
return EXIT_FAILURE;
}
int* n = reinterpret_cast(p);
*n = 0;
// 创建(如果不存在)打开二值信号量
sem_t* sem = sem_open(argv[SEM_NAME],O_CREAT|O_RDWR,0644,1);
fork();
for(int i=0;i<5;++i){
usleep(10000);
sem_wait(sem);
++*n;
cout << getpid() << ":" << *n << endl;
sem_post(sem);
}
sem_close(sem);
// 解除映射
munmap(p,sizeof(int));
return EXIT_SUCCESS;
}
可以通过注释fork()
,重新编译程序后同时执行两次程序演示两个非亲缘进程的同步。
1.2.2 匿名信号量/基于内存
No. |
操作 |
函数 |
1 |
初始化 |
int sem_init (sem_t *sem , int pshared, unsigned int value) |
2 |
销毁 |
int sem_destroy(sem_t *sem) |
3 |
挂出 |
int sem_post(sem_t *sem) |
4 |
等待 |
int sem_wait(sem_t *sem) |
5 |
尝试等待 |
int sem_trywait(sem_t *sem) |
6 |
获取信号量的值 |
int sem_getvalue(sem_t *sem, int *sval) |
其中3~4操作与命名信号量相同。
1.2.2.1 初始化
int sem_init (sem_t *sem , int pshared, unsigned int value)
No. |
参数 |
含义 |
1 |
sem |
信号量的指针 |
2 |
pshared |
共享方式。0 :线程间共享;1 :进程间共享,需要共享内存 |
3 |
value |
信号量初始值 |
No. |
返回值 |
含义 |
1 |
-1 |
出错 |
2 |
0 |
成功 |
1.2.2.2 销毁
int sem_destroy(sem_t *sem)
No. |
返回值 |
含义 |
1 |
-1 |
出错 |
2 |
0 |
成功 |
数据竞争解决方法
#include
#include
#include
#include
#include
int main(int argc,char * argv[]){
sem_t* sem = sem_open("/sem.tmp",O_CREAT|O_RDWR,0644,0);
fork();
int i=0;
for(;i<5;i++){
sleep(1);
sem_wait(sem);
printf("PID:%d,enter\n",getpid());
printf("PID:%d,do something\n",getpid());
printf("PID:%d,leave\n",getpid());
sem_post(sem);
}
}
#include
#include
#include
#include
#include
using namespace std;
struct Data{
sem_t sem;
int data;
};
int main(int argc,char* argv[]){
// 匿名共享内存
void* p = mmap(NULL,sizeof(Data),PROT_WRITE|PROT_READ,MAP_SHARED|MAP_ANON,-1,0);
if(MAP_FAILED == p){
perror("mmap error");
return EXIT_FAILURE;
}
Data* d = reinterpret_cast(p);
d->data = 0;
// 创建二值信号量
sem_init(&d->sem,1,1);
fork();
for(int i=0;i<5;++i){
usleep(10000);
sem_wait(&d->sem);
++(d->data);
cout << getpid() << ":" << d->data << endl;
sem_post(&d->sem);
}
sem_destroy(&d->sem);
munmap(p,sizeof(Data));
return EXIT_SUCCESS;
}
2. 小结
3. 计数信号量
模拟一个停车场的电子显示牌
#include
#include
#include
#include
#include
int main(int argc,char * argv[]){
sem_t* sem = sem_open(argv[1],O_CREAT,0644,atoi(argv[2]));
}
#include
#include
#include
#include
#include
#include
void handler(int sig){
printf("PID:%d,prepare to leave\n",getpid());
}
int main(int argc,char * argv[]){
signal(SIGINT,handler);
sem_t* sem = sem_open(argv[1],O_RDWR);
sem_wait(sem);
int val;
sem_getvalue(sem,&val);
printf("PID:%d,enter. park %d\n",getpid(),val);
printf("PID:%d,do something\n",getpid());
pause();
sem_post(sem);
sem_getvalue(sem,&val);
printf("PID:%d,leave. park %d\n",getpid(),val);
}
#include
#include
#include
#include
#include
#include
void handler(int sig){
printf("PID:%d,prepare to leave\n",getpid());
}
int main(int argc,char * argv[]){
signal(SIGINT,handler);
sem_t* sem = sem_open(argv[1],O_RDWR);
if(-1 == sem_trywait(sem)){
perror("no park");
return 1;
}
int val;
sem_getvalue(sem,&val);
printf("PID:%d,enter. park %d\n",getpid(),val);
printf("PID:%d,do something\n",getpid());
pause();
sem_post(sem);
sem_getvalue(sem,&val);
printf("PID:%d,leave. park %d\n",getpid(),val);
}
5. 练习
把信号量封装成一个信号量类