信号量 线程通信- Linux系统编程-(pthread)

信号量(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;
}

注:

  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;
}

————————————————
版权声明:本文为CSDN博主「_深蓝.」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_42488216/article/details/124969154

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值