【并发编程六】c++进程通信——信号量(semaphore)

news2025/8/7 7:47:56

【并发编程六】c++进程通信——信号量(semaphore)

  • 一、概述
  • 二、信号量
  • 三、原理
  • 四、过程
    • 1、进程A过程
    • 2、进程B过程
  • 五、demo
    • 1、进程A
    • 2、进程B
  • 六、输出
  • 七、windows api介绍
    • 1. 创建信号量 CreateSemaphore()
    • 2. 打开信号量 OpenSemaphore()
    • 3. 等待 WaitForSingleObject()
    • 4. 递增信号量的当前资源计数ReleaseSemaphore()
    • 5. 关闭句柄 CloseHandle()
  • 八、信号量使用场景

在这里插入图片描述

为了防⽌多进程竞争共享资源,⽽造成的数据错乱,所以需要保护机制,使得共享的资源,在任意时刻只能被⼀个进程访问(或者有限个进程访问)。正好,信号量就实现了这⼀保护机制。

一、概述

信号量的概念是由荷兰计算机科学家艾兹赫尔·戴克斯特拉(Edsger W. Dijkstra)发明的,广泛的应用于不同的操作系统中。在系统中,给予每一个进程一个信号量,代表每个进程目前的状态,未得到控制权的进程会在特定地方被强迫停下来,等待可以继续进行的信号到来。如果信号量是一个任意的整数,通常被称为计数信号量(Counting semaphore),或一般信号量(general semaphore);如果信号量只有二进制的0或1,称为二进制信号量(binary semaphore)

  • 信号量是操作系统提供的一种协调共享资源访问的方法。信号量则由操作系统进行管理,地位高于进程,操作系统保证信号量的原子性。

二、信号量

信号量(英语:semaphore)又称为信号标或者信号灯,是一个同步对象,用于保持在0至指定最大值之间的一个计数值。当线程完成一次对该semaphore对象的等待(wait)时,该计数值减一;当线程完成一次对semaphore对象的释放(release)时,计数值加一。当计数值为0,则线程等待该semaphore对象不再能成功直至该semaphore对象变成signaled状态。semaphore对象的计数值大于0,为signaled状态;计数值等于0,为nonsignaled状态。

三、原理

一个信号量 S 是个整型变量,它除了初始化外只能通过两个标准原子操作:wait () 和 signal() 来访问:

  • 操作 wait() 最初称为 P(荷兰语proberen,测试);成功则,S–;
  • 操作 signal() 最初称为 V(荷兰语verhogen,增加);成功则,S++;

四、过程

我们以windows api的接口为例,讲解下信号量是如何在进程A和进程B间做到进程间同步的。

1、进程A过程

  • 1.1、CreateSemaphore():创建一个名字为Semaphore的信号量,该信号量初始可使用的资源数为0。即S=0.
  • 1.2、WaitForSingleObject():等待信号量>0,就是等待信号量的资源数大于0时。成功后就是S–。(启动进程A后,此处会一直等待,因为创建的信号量初始的值=0,直到进程B打开进程A的信号量,并且释放一个可以使用的资源时,S变成1,才可以继续,进行后面的程序
  • 1.3、在屏幕打印文字。
  • 1.4、ReleaseSemaphore():释放上面wait成功时占用的1个资源数。执行成功后就是S++。
  • 1.5、等待5s。

2、进程B过程

  • 2.1、OpenSemaphore():打开进程A创建的信号量,名字为Semaphore
  • 2.2、ReleaseSemaphore():递增信号量的当前资源计数,就是S++。S=1
  • 2.3、WaitForSingleObject():等待信号量>0,就是等待信号量的资源数大于0时。成功后就是S–。
  • 2.4、在屏幕打印文字。
  • 2.5、ReleaseSemaphore():释放上面wait成功时占用的1个资源数。成功后就是S++。
  • 2.6、等待5s。

五、demo

1、进程A

  • main.cpp
#include <windows.h>
#include <iostream> 
using namespace std;

#define BUF_SIZE 4096

int main(int argc, TCHAR* argv[])
{
    cout << "processA:" << endl << endl;

    // 创建信号量
    HANDLE handle = CreateSemaphore(
        NULL,
        0,
        10,
        "Semaphore"
    );

    cout << "创建信号量成功,并等待进程B释放一个可以使用的资源数......." << endl<<endl;
    for (int i = 0; i < 5; i++)
    {
        cout << "进入新号量等待区" << endl;
        DWORD ret = WaitForSingleObject(
            handle,
            100000
        );
        if (0 == ret)
        {
            cout << "processA:" << i << ".....s--" << endl;
        }
        ReleaseSemaphore(handle , 1,NULL);
        cout << "释放1个信号并等待5秒中....." << "s++" << endl << endl;
        Sleep(5000);
    } 
    system("pause"); //等待其他进程读取数据

    // 关闭句柄
    CloseHandle(handle);
    return 0;
}

  • CmakeList.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0)
PROJECT(process)
ADD_EXECUTABLE(processA main.cpp)
ADD_SUBDIRECTORY(processB)
SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")

2、进程B

  • main.cpp
#include <iostream>  
#include <windows.h>  
using namespace std;

#define BUF_SIZE 4096

int main()
{
    cout << "processB:" << endl << endl;

     // 打开共享的文件对象
    HANDLE handle = OpenSemaphore(
    SEMAPHORE_ALL_ACCESS,          // 访问标志
    TRUE,                          // 继承标志
    "Semaphore");                   // 信号量名
    if (nullptr != handle)
    {
        cout << "打开信号量:成功" << endl;
    }
    else
    {
        cout << "打开信号量:失败" << endl;
    }

    cout << "释放1个可以使用的资源数:s++" << endl<<endl;
    ReleaseSemaphore(handle, 1, NULL);
    
    for (int i = 0; i < 5; i++)
    {
        cout << "进入新号量等待区" << endl;
        DWORD ret = WaitForSingleObject(
            handle,
            100000
        );
        if (0 == ret)
        {
            cout << "processB:" << i <<".....s--"<< endl;
        }
        ReleaseSemaphore(handle, 1, NULL);
        cout << "释放1个信号并等待5秒中....." << "s++" << endl << endl;
        Sleep(5000);

    }

    system("pause");
    return 0;
}

  • CmakeLists.txt
CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0)

SET(TARGET "childprocess")

ADD_EXECUTABLE(processB main.cpp)

SET(LIBRARY_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")

六、输出

构建、编译、先启动进程A,输出如下。(备:因为创建信号量时,初始化资源数为0,即S=0,如果你不启动进程B释放可用的资源数,进程A在此处会等待直到超时)

在这里插入图片描述

再启动进程B输出如下。

在这里插入图片描述

七、windows api介绍

本文我们以windows的api接口为例,其实Linux下接口原理是一样的,用法也类似,我们不再过多介绍。

1. 创建信号量 CreateSemaphore()

函数说明:

第一个参数表示安全控制,一般直接传入NULL。
第二个参数表示初始资源数量。
第三个参数表示最大并发数量。
第四个参数表示信号量的名称,传入NULL表示匿名信号量。

HANDLE WINAPI CreateSemaphore(  
  _In_opt_  LPSECURITY_ATTRIBUTES lpEventAttributes,   // 安全属性,通常为NULL
  _In_      LONG lInitialCount,                        // 可用资源数目,就是信号量S的初始值
  _In_      LONG lMaximumCount,                        // 信号量对象可处理的最大资源数,就是S的最大值
  _In_opt_  LPCTSTR lpName                             // 信号量的名称,进程之间可共享
);  

2. 打开信号量 OpenSemaphore()

函数说明:

第一个参数表示访问权限,对一般传入SEMAPHORE_ALL_ACCESS。详细解释可以查看MSDN文档。
第二个参数表示信号量句柄继承性,一般传入TRUE即可。
第三个参数表示名称,不同进程中的各线程可以通过名称来确保它们访问同一个信号量。

HANDLE WINAPI OpenSemaphore(  
  _In_  DWORD dwDesiredAccess,   // 访问的权限
  _In_  BOOL bInheritHandle,     // 子进程继承信号量对象与否
  _In_  LPCTSTR lpName           // 信号量对象名称
); 

3. 等待 WaitForSingleObject()

等待指定对象处于信号状态或超时间隔已过。
本文中,就是等待信号量S>0时,进入。如果信号量S<=0,那就是等待,直到设置的超时时间。

DWORD WINAPI WaitForSingleObject(  
  _In_  HANDLE hHandle,                   // 内核对象句柄
  _In_  DWORD dwMilliseconds              // 单位,毫秒。对象被触发前的等待时间,INFINITE表示无限时间阻塞等待,简单来说就是死等
);  

4. 递增信号量的当前资源计数ReleaseSemaphore()

函数说明:

第一个参数是信号量的句柄。
第二个参数表示增加个数,必须大于0且不超过最大资源数量。
第三个参数可以用来传出先前的资源计数,设为NULL表示不需要传出。

BOOL WINAPI ReleaseSemaphore(  
  _In_       HANDLE hSemaphore,    // 信号量对象句柄
  _In_       LONG lReleaseCount,   // 释放使用的资源树
  _Out_opt_  LPLONG lpPreviousCount   // 一般设为null
);  

当一个线程使用完信号量对象控制的有限资源后,应该调用ReleaseSemaphore,释放使用的资源,使信号量对象的当前资源计数得到恢复

5. 关闭句柄 CloseHandle()

BOOL WINAPI CloseHandle(  
  _In_  HANDLE hObject    // 句柄
); 

八、信号量使用场景

  • 多进程访问统一资源。
  • 资源限流。
    比如:数据库连接池,同时进行连接的线程有数量限制,连接不能超过一定的数量,当连接达到了限制数量后,后面的线程只能排队等前面的线程释放了数据库连接才能获得数据库连接。

关于资源限流,我们举例说下。
1、一个厕所有3个坑,进厕所需要拿1个号牌(5毛钱一个号牌)。
2、肯定要设置为信号量的初始值为S=3,就是厕所的号牌数量为3。
3、进去一个人,号牌减1,S–。
4、当进入3个人时,没有号牌了,S=0,第四个人就要等待了,
5、等待厕所中的其中一个人上完大号出来,把号牌交出去,S++,其他人才可以拿着号牌进去。
(其实,你想想网站服务器控制并发访问和限流,不也是这个原理吗?)

  • 参考
    1、多线程面试题系列(8):经典线程同步 信号量Semaphore
    2、 信号量与管程
    3、 信号量及其使用和实现(超详细)
    4、 维基百科-信号量
    4、 信号量限流,高并发场景不得不说的秘密

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/15662.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

力扣LeatCode算法题第三题-无重复字符的最长子串

要求&#xff1a; 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长子串 的长度。 我一开始采用的第一种方法是使用hashmap去比对大小&#xff0c;在idea上可以跑通程序&#xff0c;但在leatcode的编译器中&#xff0c;无法通过字符串s"" 和s"…

苹果推送和开发证书更新

1.背景 推送证书&#xff08;生产Apple Push Services和开发APNs Development iOS&#xff09;的有效期都是一年&#xff0c;将要过期的时候&#xff0c;苹果官方会发邮件提醒。 2.csr和推送证书更新 打开mac电脑&#xff0c;找到启动台-->其他&#xff0c;打开钥匙串访问。…

uni-app入门:WXML数据绑定

1.简单数据绑定 2.组件属性数据绑定 3.运算绑定 3.1三元运算符 3.2算数运算 3.3字符串拼接运算 3.4逻辑判断运算 正文 WXML全称&#xff1a;wexin markup language,微信标签语言&#xff0c;可以理解为web中的html&#xff0c;…

JavaEE之HTTP协议 Ⅱ

文章目录一、认识请求"报头"(header)1.HOST2.Content-Length3.Content-Type4.User-Agent (简称 UA)5.Referer4.Cookie二、HTTP响应详解1. 认识"状态码"(status code)2. Content-Type3. 如何构造HTTP请求总结一、认识请求"报头"(header) 这里的键…

php资源列表|开发者应知晓

PHP PSR 代码标准 官网: https://www.php-fig.org原文: https://github.com/php-fig/fi...中文: https://psr.phphub.orgPHP资源列表PHP设计模式PHP知识技能树PHP资源站 PHP WeeklyCodeVisuallyPHP LeagueKnpLabs开发项目 FastAdmin - 基于 ThinkPHP5 + Bootstrap 的极速后台开…

ubuntu搭建Elasticsearch过程与问题

目录 一.下载Elasticsearh 二.解压 创建需要的文件目录 三.修改配置文件 四.遇到的问题 1.root账号启动问题 2.创建文件不授权切换到非root用户test的时候报错 3.启动最后还是报错 4.浏览器请求http://localhost:9200 报错&#xff1a;received plaintext http traffic…

【python】算法与数据结构作业记录分析

目录 算法与数据结构实验题 9.21 朋友圈 ★实验任务 ★数据输入 ★数据输出 输入示例 输出示例 代码实现 效果展示 算法与数据结构实验题 9.24 水杯 ★实验任务 ★数据输入 ★数据输出 输入示例 输出示例 代码实现 效果展示 算法与数据结构实验题 9.21 朋友圈 …

实验(四):LCD1602显示实验

一、实验目的与任务 实验目的&#xff1a; 1. 掌握LCD1602显示控制方法&#xff1b; 2. 掌握利用Proteus进行单片机控制系统的仿真及调试方法。 3. 掌握单片机开发板的使用。 任务&#xff1a; 1.根据要求编写程序&#xff0c;并写出原理性注释&#xff1b; 2. 将检查程序运行的…

Java入门项目——读书管理系统

Java简单实现读书管理系统一、前言二、思路及整体框架三、代码展示1.有关读书包&#xff08;Book&#xff09;2.有关用户包3.有关操作书的包一、前言 相信有很多小伙伴学习完了【JavaSE】基础语法&#xff0c;想知道自己到底学的怎么样&#xff0c;或则学完不知道这么把知识点…

JavaFX之Scene Builder的使用(开发一款GUI小工具原来这么简单)

文章目录一、前言二、JavaFX与Scene Builder下载三、Scene Builder的使用四、详细教学&#xff08;示例&#xff09;4.1 环境配置4.2 创建fxml文件以及Controller类文件4.3 自定义界面4.4 运行我们的程序五、拓展总结博主个人社区&#xff1a;开发与算法学习社区 博主个人主页&…

创建.gitignore文件并使用

创建 .gitignore文件 第一种方式 在项目根目录下直接创建一个文件&#xff0c;后缀改成 .gitignore 即可。 第二种方式 用git创建&#xff0c;到根目录下&#xff0c;执行 touch .gitignore&#xff0c;即可看见目录下已经出现了该忽略文件。 添加忽略规则 # 忽略所有以 …

httpOnly对于抵御Session劫持的个人小结

Ⅰ 什么是http only?起到什么防护作用&#xff1f; cookie中设置了HttpOnly属性&#xff0c;那么通过js脚本将无法读取到cookie信息&#xff0c;主要防护的攻击手段&#xff1a;XSS不能通过document对象直接获取cookie Ⅱ 怎么绕过http only的防护&#xff08;三种&#xff…

5、CSS——三种样式和样式优先级、CSS中颜色设置方式、标签选择器和基本选择器的优先级

目录 一、行内样式 二、内部样式 三、外部样式 1、创建外部样式步骤 2、引入外部样式的两种方式 2.1 第一种 2.2 第二种 3、style标签内的注释符号 四、样式优先级 五、CSS中颜色设置方式 1、使用颜色的英文单词 2、使用十六进制表示法 3、使用rgb()表示法…

Redis分区/分片详解

分区/分片详解 分区是分割数据到多个Redis实例的处理过程&#xff0c;因此每个实例只保存key的一个子集。 如果只使用一个redis实例时&#xff0c;其中保存了服务器中全部的缓存数据&#xff0c;这样会有很大风险&#xff0c;如果单台redis服务宕机了将会影响到整个服务。解决的…

easyExcel不同版本按照模板导出

Java解析、生成Excel比较有名的框架有Apache poi、jxl。但他们都存在一个严重的问题就是非常的耗内存&#xff0c;poi有一套SAX模式的API可以一定程度的解决一些内存溢出的问题&#xff0c;但POI还是有一些缺陷&#xff0c;比如07版Excel解压缩以及解压后存储都是在内存中完成的…

力扣(LeetCode)7. 整数反转(C++)

模拟 整数反转&#xff0c;需要一个中间变量 ansansans &#xff0c; 循环存入 xxx 的最低位 x%10x\%10x%10&#xff0c; 然后 xx/10x x/10xx/10 &#xff0c;得到 xxx 新的最低位 。如果进入新的循环&#xff0c;ans10ans\times 10ans10 &#xff0c;让上一次的 ansansans 十…

Makefile 基础(一) —— 自定义变量、预定义变量、自动变量

目录 一、自定义变量 1、定义变量 2、使用变量 3、修改变量的值 二、预定义变量 三、自动变量 一、自定义变量 1、定义变量 变量定义有两种方式&#xff0c;一种会在使用的时候递归展开&#xff0c;一种是直接赋值。两种定义方式如下&#xff1a; 递归展开&#xff1a…

Servlet的基本使用

目录 一、Servlet是什么 二、Servlet的基本使用 1、创建项目 2、引入依赖 3、创建目录 4、编写代码 5、打包程序 6、部署程序 7、验证程序 三、优化部署方式 1、安装Smart Tomcat 2、使用Smart Tomcat 四、使用Servlet时常见的错误 1、404 2、405 3、500 4、…

Linux文件目录之查看篇【cat、more、less、head、tail、>、>>】【简直不要太详细】

目录cat 查看文件内容morelessecho&#xff1a;将输入内容到控制台>指令和>>指令&#xff1a; >输出重定向 &#xff0c;>>追加head:tailcat 查看文件内容 cat【选项】 【要查看的文件】 -n 显示行号 注意&#xff1a;cat只能浏览文件&#xff0c;并不能修…

第二章--应用层

2.1应用层协议原理 研发网络应用程序的核心是写出能够运行在不同的端系统和同构网络彼此通信的程序&#xff0c;将应用软件限制在端系统&#xff0c;从而促进大量的网络应用程序的迅速研发和部署。 2.1.1网络应用程序体系结构 应用程序的体系不同于网络的体系结构&#xff0c…