• 信号量 线程通信- 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:父子进程出现数据竞争

    1. #include <iostream>
    2. #include <unistd.h>
    3. #include <sys/mman.h>
    4. #include <sys/wait.h>
    5. #include <cstdio>
    6. #include <fcntl.h>
    7. using namespace std;
    8. int main(){
    9.     int* p = (int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
    10.     if(MAP_FAILED == 0){
    11.          perror("mmap error");
    12.          return 1;
    13.     }
    14.     *p = 0;
    15.     if(fork()){
    16.         for(int i = 0;i < 10000;++i){
    17.             cout << "parent:" << p << ":" << ++*p << endl;
    18.         }
    19.         wait(NULL);
    20.     }else{
    21.         for(int i = 0;i < 10000;++i){
    22.             cout << "child:" << p << ":" << --*p << endl;
    23.         }
    24.     }
    25.     munmap(p,sizeof(int));
    26.     p = NULL;
    27. }

    数据竞争导致父子进程访问同一片内存时出错。

    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)加入命名信号量,同一时间只允许单个进程进行读写操作。

    1. #include <iostream>
    2. #include <unistd.h>
    3. #include <sys/mman.h>
    4. #include <sys/wait.h>
    5. #include <cstdio>
    6. #include <fcntl.h>
    7. using namespace std;
    8. int main(){
    9.     int* p = (int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
    10.     if(MAP_FAILED == 0){
    11.          perror("mmap error");
    12.          return 1;
    13.     }
    14.     *p = 0;
    15.     if(fork()){
    16.         for(int i = 0;i < 10000;++i){
    17.             cout << "parent:" << p << ":" << ++*p << endl;
    18.         }
    19.         wait(NULL);
    20.     }else{
    21.         for(int i = 0;i < 10000;++i){
    22.             cout << "child:" << p << ":" << --*p << endl;
    23.         }
    24.     }
    25.     munmap(p,sizeof(int));
    26.     p = NULL;
    27. }          

    (2)匿名信号量的方式。

    1. #include <iostream>
    2. #include <unistd.h>
    3. #include <sys/mman.h>
    4. #include <sys/wait.h>
    5. #include <cstdio>
    6. #include <fcntl.h>
    7. #include <semaphore.h>
    8. #include <sstream>
    9. using namespace std;
    10. int main() {
    11.     sem_t* psem = (sem_t*)mmap(NULL,sizeof(sem_t),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
    12.     sem_init(psem,1,1);//初始化,前面的1表示进程之间共享,后面1表示值
    13.     if(SEM_FAILED == psem) {
    14.         perror("sem_open error");
    15.         return 1;
    16.     }
    17.     int* p = (int*)mmap(NULL,sizeof(int),PROT_READ|PROT_WRITE,MAP_SHARED|MAP_ANON,-1,0);
    18.     if(MAP_FAILED == p) {
    19.         perror("mmap error");
    20.         return 1;
    21.     }
    22.     *p = 0;
    23.     fork();
    24.     for(int i = 0; i < 2000; ++i) {
    25.         sem_wait(psem);
    26.         ostringstream oss;
    27.         oss << getpid() << " :"<< --*p << endl;
    28.         string s = oss.str();
    29.         write(STDOUT_FILENO,s.c_str(),s.size()+1);
    30.         sem_post(psem);
    31.     }
    32.     munmap(p,sizeof(int));
    33.     sem_destroy(psem);
    34.     psem = NULL;
    35.     p = NULL;
    36. }

    注:

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

    5.5.2 实例2:加入信号量的read.cpp

    1. #include <iostream>
    2. #include <unistd.h>
    3. #include <sys/mman.h>
    4. #include <sys/shm.h>
    5. #include <fcntl.h>
    6. #include <semaphore.h>
    7. using namespace std;
    8. int main(int argc,char* argv[]){
    9.     if(2 != argc){
    10.         cout << "arguement error" << endl;
    11.         cout << "Usage: " << argv[0] << "shm file" << endl;
    12.         return 1;
    13.     }
    14.     int fd = shm_open(argv[1],O_RDWR|O_CREAT,0666);
    15.     if(-1 == fd){
    16.         perror("open error");
    17.         return 1;
    18.     }
    19.     sem_t* psem = sem_open("/sem.test",O_RDWR,0666,1);
    20.     int* p = (int*)mmap(NULL,sizeof(int),PROT_WRITE|PROT_READ,MAP_SHARED,fd,0);
    21.     if(MAP_FAILED == p){
    22.         perror("map error");
    23.         return 1;
    24.     }
    25.     for(int i = 0;i < 100;++i){
    26.         sem_wait(psem);
    27.         cout << getpid() << "read:" << --*p << endl;
    28.         sem_post(psem);
    29.     }
    30.     munmap(p,sizeof(int));
    31.     p = NULL;
    32.     sem_close(psem);
    33.     psem = NULL;
    34. }

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

  • 相关阅读:
    在react中封装一个简单的自定义hook
    Golang 常用的开源库和学习资源有哪些
    带权并查集
    学生信息管理系统(spring+springmvc+mybatis+Layui)
    无代码开发打印模板入门教程
    软件测试我培训完两周了,现在只面试了一家公司,上海这边今年招测试的公司好少,好焦虑啊,怎么办?
    Xilinx DMA的几种方式与架构
    环氧聚醚接枝胺化聚苯乙烯微球/AM型聚苯乙烯微球氧氟沙星聚合物的制备
    云原生之旅 - 11)基于 Kubernetes 动态伸缩 Jenkins Build Agents
    Mybatis-Plus的介绍和使用
  • 原文地址:https://blog.csdn.net/u012294613/article/details/126485970