五、Linux系统编程:信号量

5 信号量

信号量(Semaphore),有时被称为信号灯,是在多线程环境下使用的一种设施,是可以用来保证两个或多个关键代码段不被并发调用。

  • 背景
    数据竞争
  • 作用:
    控制多进程共享资源的访问(资源有限并且不共享)
  • 本质
    任一时刻只能有一个进程访问临界区(代码),数据更新的代码。
  • 基本操作:PV
    原子操作操作也被成为PV原语(P来源于Dutchproberen"测试",V来源于Dutchverhogen"增加")
    在这里插入图片描述
  • 信号
分类取值
P(信号量)0:挂起进程;>0:减1
V(信号量)0:恢复进程;>0:加1
  • POSIX信号量
    查看:man sem_overview

5.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)
  • 参数
参数含义
name信号量IPC名字
oflag标志
mode权限位
value信号量初始值
  • 返回值
返回值含义
SEM_FAILED信号量的指针
SEM_FAILED出错

5.2.2 删除

int sem_unlink(const char *name)
  • 参数:
    name: 信号量IPC名字

  • 返回值

返回值含义
-1出错
0成功

5.2.3 打开

sem_t *sem_open(const char *name, int oflag)
  • 参数:
参数含义
name信号量IPC名字
oflag标志
  • 返回值
返回值含义
SEM_FAILED信号量的指针
SEM_FAILED出错

5.2.4 关闭

int sem_close(sem_t *sem)
  • 参数:
    sem:信号量的指针

  • 返回值

返回值含义
-1出错
0成功

5.2.5 挂出

int sem_post(sem_t *sem)
  • 参数:
    sem:信号量的指针

  • 返回值

返回值含义
-1出错
0成功

5.2.6 等待

int sem_wait(sem_t *sem)
  • 参数:
    sem:信号量的指针

  • 返回值

返回值含义
-1出错
0成功

5.2.7 尝试等待

int sem_trywait(sem_t *sem)
  • 参数:
    sem:信号量的指针

  • 返回值

返回值含义
-1出错
0成功

5.2.8 获取信号量的值

int sem_getvalue(sem_t *sem, int *sval)
  • 参数:
参数含义
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)
  • 参数
参数含义
sem信号量的指针
pshared共享方式。0:线程间共享;1:进程间共享,需要共享内存
value信号量初始值
  • 返回值
返回值含义
-1出错
0成功

5.3.2 销毁

int sem_destroy(sem_t *sem)
  • 参数:
    sem:信号量的指针

  • 返回值

返回值含义
-1出错
0成功

5.4 命名信号量 vs. 匿名信号量

在这里插入图片描述

  • 功能区别:
    1、命名信号量一般是用在进程间同步,匿名信号量一般用在线程间同步。
    2、命名信号量,其值保存在文件中, 所以它可以用于线程也可以用于进程间的同步。
    3、匿名信号量,其值保存在内存中。

  • 代码区别:
    主要在于两种信号量初始化和销毁的方式不同。

5.5 实例

5.5.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>
#include <semaphore.h>
#include <sstream>
using namespace std;
int main() {
    sem_t* psem = sem_open("/sem.test",O_CREAT|O_RDWR,0666,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 < 10000; ++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_close(psem);
    psem = NULL;
    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;
}

注:
1、编译时加-lrt -pthread
2、父子进程之间可以用匿名方式,不同进程之间只能用命名方式。

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;
}
  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值