Linux 信号量

Linux 信号量

  • 一、信号量的基本概念
    • 1. 计数信号量(Counting Semaphore)
    • 2. 二进制信号量(Binary Semaphore)
  • 二 、使用场景
    • 1. 信号量需要用到的库
      • 系统V IPC头文件
    • 2. 代码演示
      • 1. 头文件和结构体定义
      • 2. 主函数
      • 3. 创建/获取共享内存
      • 4. 连接共享内存到当前进程的地址空间
      • 5. 创建、初始化二元信号量
      • 6. 加锁和访问共享内存
      • 7. 解锁和清理
      • 8. 全部代码

一、信号量的基本概念

信号量(Semaphore)是操作系统中用于管理并发进程的一种同步机制。它们用于控制对共享资源的访问,以避免竞争条件和数据不一致的问题。信号量主要分为两种类型:计数信号量(Counting Semaphore)和二进制信号量(Binary Semaphore),又称互斥量(Mutex)。

1. 计数信号量(Counting Semaphore)

计数信号量是一个整数值,可以用于控制对多个资源的访问。它的初始值表示可用资源的数量。计数信号量支持两种基本操作:

  • P(wait)操作:试图将信号量的值减1。如果信号量的值大于0,则操作成功;如果信号量的值为0,则进程会阻塞,直到信号量的值大于0。
  • V(signal)操作:将信号量的值加1。如果有进程因信号量的值为0而阻塞,则会唤醒其中的一个进程。

2. 二进制信号量(Binary Semaphore)

二进制信号量只有0和1两个值,常用于实现互斥访问。它也支持P和V操作,但由于值只能为0或1,因此操作更简单:

  • P(wait)操作:如果信号量的值为1,则将其置为0,并允许进程进入临界区;如果信号量的值为0,则进程会阻塞,直到信号量的值为1。
  • V(signal)操作:将信号量的值置为1,并唤醒因等待信号量而阻塞的进程。

二 、使用场景

  • 进程同步:确保多个进程在访问共享资源时不会发生冲突。
  • 资源计数:例如,限制同时访问某个资源的线程或进程数量。
  • 生产者-消费者问题:用来协调生产者和消费者之间的生产和消费速度。

1. 信号量需要用到的库

系统V IPC头文件

  1. #include <sys/ipc.h>

    提供了System V IPC(进程间通信)机制的通用接口,包括消息队列、信号量和共享内存。
    主要用于生成IPC键值(ftok 函数)和定义IPC相关的常量。

  2. #include <sys/shm.h>

    提供了System V共享内存的接口,包括创建、连接、分离和控制共享内存段的函数。
    常用函数包括 shmgetshmatshmdtshmctl 等。

  3. #include <sys/types.h>

    定义了许多数据类型,用于其他系统调用接口。例如,pid_t(进程ID)、key_t(IPC键值)、size_t(对象大小)等。
    提供了一些POSIX标准定义的类型,用于与系统调用进行交互。

  4. #include <sys/sem.h>

    提供了System V信号量的接口,包括创建、操作和控制信号量集的函数。
    常用函数包括 semgetsemopsemctl 等。

2. 代码演示

以下例程来自b站公开课,C++中高级程序员实战(码农联盟)

这段代码演示了如何使用信号量对共享内存进行加锁,以确保对共享内存的访问是线程安全的。下面是对这段代码的详细分析

1. 头文件和结构体定义

这里头文件里存放了以下函数声明,csemp 类的声明和定义,用于信号量操作。

#include "_public.h"struct stgirl     // 超女结构体。
{int  no;        // 编号。char name[51];  // 姓名,注意,不能用string。
};
  • 包含必要的头文件 _public.h。
  • 定义一个结构体 stgirl,包含两个成员:no(编号)和 name(姓名)。注意,这里不能使用C++的 std::string 类型,而是使用了C风格的字符数组。

2. 主函数

int main(int argc, char *argv[])
{if (argc != 3) { cout << "Using:./demo no name\n"; return -1; }
  • 检查命令行参数是否正确(应包含两个参数:编号和姓名)。如果参数数量不正确,输出使用方法并返回错误。

3. 创建/获取共享内存

相关的如 shmget() 函数的介绍放在我另外一个笔记中:Linux 共享内存

  int shmid = shmget(0x5005, sizeof(stgirl), 0640 | IPC_CREAT);if (shmid == -1) {cout << "shmget(0x5005) failed.\n";return -1;}cout << "shmid=" << shmid << endl;
  • 使用 shmget 创建或获取一个共享内存段,键值为 0x5005,大小为 stgirl 结构体的大小,权限为 0640。如果不存在则创建。
  • 如果 shmget 失败,输出错误信息并返回。

补充

组合使用权限标志

  • 在上述示例中,IPC_CREAT 常常与权限标志组合使用,如 0644 或 0666。这些权限标志类似于文件权限,定义了对 IPC 资源的读写权限。
  • 0644:所有者读写权限,其他用户只读权限。
  • 0666:所有用户都具有读写权限。

0640 权限表示:

  • 所有者(Owner):读(r)+ 写(w),即6。
  • 所属组(Group):读(r),即4。
  • 其他用户(Others):没有权限(0)。
  • 这个权限设置保证了对IPC资源的合理访问控制,防止未授权用户对资源进行读写操作。

4. 连接共享内存到当前进程的地址空间

  stgirl *ptr = (stgirl *)shmat(shmid, 0, 0);if (ptr == (void *)-1) {cout << "shmat() failed\n";return -1;}
  • 使用 shmat 将共享内存连接到当前进程的地址空间。
  • 如果 shmat 失败,输出错误信息并返回-1。( shmat 返回 (void *)-1,则表示连接失败。)

5. 创建、初始化二元信号量

  csemp mutex;if (mutex.init(0x5005) == false) {cout << "mutex.init(0x5005) failed.\n";return -1;}
  • 创建一个信号量 mutex。
  • 初始化信号量,使用与共享内存相同的键值 0x5005。
  • 如果初始化失败,输出错误信息并返回。

6. 加锁和访问共享内存

  cout << "申请加锁...\n";mutex.wait(); // 申请加锁。cout << "申请加锁成功。\n";cout << "原值:no=" << ptr->no << ",name=" << ptr->name << endl;  // 显示共享内存中的原值。ptr->no = atoi(argv[1]);        // 对超女结构体的no成员赋值。strcpy(ptr->name, argv[2]);    // 对超女结构体的name成员赋值。cout << "新值:no=" << ptr->no << ",name=" << ptr->name << endl;  // 显示共享内存中的当前值。sleep(10);
  • 输出加锁申请信息,并调用 mutex.wait() 进行加锁。
  • 显示共享内存中的原值。
  • 更新 stgirl 结构体的 no 和 name 成员为命令行参数提供的值。
  • 显示更新后的值。
  • 休眠10秒,模拟处理过程。

其中 wait() 是 信号量的P操作(把信号量的值减value),如果信号量的值是0,将阻塞等待,直到信号量的值大于0。

// 信号量的P操作(把信号量的值减value),如果信号量的值是0,将阻塞等待,直到信号量的值大于0。
bool csemp::wait(short value)
{if (m_semid==-1) return false;struct sembuf sem_b;sem_b.sem_num = 0;      // 信号量编号,0代表第一个信号量。sem_b.sem_op = value;   // P操作的value必须小于0。sem_b.sem_flg = m_sem_flg;if (semop(m_semid,&sem_b,1) == -1) { perror("p semop()"); return false; }return true;
}

atoi 函数
atoi(ASCII to Integer)函数是C标准库中的一个函数,用于将C风格的字符串转换为整数。它的声明在头文件 中。
在 main 函数中,命令行参数会被存储在 argv 数组中:

argv[0] 存储程序的名称,即 ./demo。
argv[1] 存储第一个参数,即 “12345”。
argv[2] 存储第二个参数,即 “John”。
atoi(argv[1]); 的作用是将字符串 “12345” 转换为整数 12345。

在这段代码中,atoi(argv[1]) 的具体作用如下:

  • 将命令行参数 argv[1](即字符串 “12345”)转换为整数 12345。
  • 然后,将该整数赋值给结构体 stgirl 的成员 no:ptr->no = atoi(argv[1]);。

7. 解锁和清理

  mutex.post(); // 解锁。cout << "解锁。\n";// 查看信号量  :ipcs -s    // 删除信号量  :ipcrm sem 信号量id// 查看共享内存:ipcs -m    // 删除共享内存:ipcrm -m  共享内存id// 第4步:把共享内存从当前进程中分离。shmdt(ptr);// 第5步:删除共享内存。if (shmctl(shmid, IPC_RMID, 0) == -1) {cout << "shmctl failed\n";return -1;}
}
  • 调用 mutex.post() 进行解锁。
  • 输出解锁信息。
  • 提示用户如何查看和删除信号量及共享内存。
  • 使用 shmdt 将共享内存从当前进程中分离。
  • 删除共享内存。

mutex.post() 表示对信号量进行V操作,解锁对共享资源的访问。具体来说,当一个进程完成对共享资源的操作后,它会调用 mutex.post() 来释放信号量,使得其他等待该信号量的进程可以继续执行。
mutex是我们之前用类创建的对象,它去调用post()函数

post函数

// 信号量的V操作(把信号量的值减value)。
bool csemp::post(short value)
{if (m_semid==-1) return false;struct sembuf sem_b;sem_b.sem_num = 0;     // 信号量编号,0代表第一个信号量。sem_b.sem_op = value;  // V操作的value必须大于0。sem_b.sem_flg = m_sem_flg;if (semop(m_semid,&sem_b,1) == -1) { perror("V semop()"); return false; }return true;
}

8. 全部代码

_public.h

#ifndef __PUBLIC_HH
#define __PUBLIC_HH 1#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <sys/types.h>
#include <sys/sem.h>
using namespace std;// 信号量。
class csemp
{
private:union semun  // 用于信号量操作的共同体。  操作信号量需要这样的数据结构{int val;struct semid_ds *buf;unsigned short  *arry;};int   m_semid;         // 信号量id(描述符)。// 如果把sem_flg设置为SEM_UNDO,操作系统将跟踪进程对信号量的修改情况,// 在全部修改过信号量的进程(正常或异常)终止后,操作系统将把信号量恢复为初始值。// 如果信号量用于互斥锁,设置为SEM_UNDO。// 如果信号量用于生产消费者模型,设置为0。short m_sem_flg;csemp(const csemp &) = delete;             // 禁用拷贝构造函数。csemp &operator=(const csemp &) = delete;  // 禁用赋值函数。
public:csemp():m_semid(-1){}// 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。// 如果用于互斥锁,value填1,sem_flg填SEM_UNDO。// 如果用于生产消费者模型,value填0,sem_flg填0。bool init(key_t key,unsigned short value=1,short sem_flg=SEM_UNDO);bool wait(short value=-1);// 信号量的P操作,如果信号量的值是0,将阻塞等待,直到信号量的值大于0。bool post(short value=1); // 信号量的V操作。int  getvalue();           // 获取信号量的值,成功返回信号量的值,失败返回-1。bool destroy();            // 销毁信号量。~csemp();
};#endif

_public.cpp

#include "_public.h"// 如果信号量已存在,获取信号量;如果信号量不存在,则创建它并初始化为value。
// 如果用于互斥锁,value填1,sem_flg填SEM_UNDO。
// 如果用于生产消费者模型,value填0,sem_flg填0。
bool csemp::init(key_t key,unsigned short value,short sem_flg)
{if (m_semid!=-1) return false; // 如果已经初始化了,不必再次初始化。m_sem_flg=sem_flg;// 信号量的初始化不能直接用semget(key,1,0666|IPC_CREAT)// 因为信号量创建后,初始值是0,如果用于互斥锁,需要把它的初始值设置为1,// 而获取信号量则不需要设置初始值,所以,创建信号量和获取信号量的流程不同。// 信号量的初始化分三个步骤:// 1)获取信号量,如果成功,函数返回。// 2)如果失败,则创建信号量。// 3) 设置信号量的初始值。// 获取信号量。if ( (m_semid=semget(key,1,0666)) == -1){// 如果信号量不存在,创建它。if (errno==ENOENT){// 用IPC_EXCL标志确保只有一个进程创建并初始化信号量,其它进程只能获取。if ( (m_semid=semget(key,1,0666|IPC_CREAT|IPC_EXCL)) == -1){if (errno==EEXIST) // 如果错误代码是信号量已存在,则再次获取信号量。{if ( (m_semid=semget(key,1,0666)) == -1){ perror("init 1 semget()"); return false; }return true;}else  // 如果是其它错误,返回失败。{perror("init 2 semget()"); return false;}}// 信号量创建成功后,还需要把它初始化成value。union semun sem_union;sem_union.val = value;   // 设置信号量的初始值。if (semctl(m_semid,0,SETVAL,sem_union) <  0) { perror("init semctl()"); return false; }}else{ perror("init 3 semget()"); return false; }}return true;
}// 信号量的P操作(把信号量的值减value),如果信号量的值是0,将阻塞等待,直到信号量的值大于0。
bool csemp::wait(short value)
{if (m_semid==-1) return false;struct sembuf sem_b;sem_b.sem_num = 0;      // 信号量编号,0代表第一个信号量。sem_b.sem_op = value;   // P操作的value必须小于0。sem_b.sem_flg = m_sem_flg;if (semop(m_semid,&sem_b,1) == -1) { perror("p semop()"); return false; }return true;
}// 信号量的V操作(把信号量的值减value)。
bool csemp::post(short value)
{if (m_semid==-1) return false;struct sembuf sem_b;sem_b.sem_num = 0;     // 信号量编号,0代表第一个信号量。sem_b.sem_op = value;  // V操作的value必须大于0。sem_b.sem_flg = m_sem_flg;if (semop(m_semid,&sem_b,1) == -1) { perror("V semop()"); return false; }return true;
}// 获取信号量的值,成功返回信号量的值,失败返回-1。
int csemp::getvalue()
{return semctl(m_semid,0,GETVAL);
}// 销毁信号量。
bool csemp::destroy()
{if (m_semid==-1) return false;if (semctl(m_semid,0,IPC_RMID) == -1) { perror("destroy semctl()"); return false; }return true;
}csemp::~csemp()
{
}

demo3.cpp

// demo3.cpp,本程序演示用信号量给共享内存加锁。
#include "_public.h"struct stgirl     // 超女结构体。
{int  no;        // 编号。char name[51];  // 姓名,注意,不能用string。
};int main(int argc,char *argv[])
{if (argc!=3) { cout << "Using:./demo no name\n"; return -1; }// 第1步:创建/获取共享内存,键值key为0x5005,也可以用其它的值。int shmid=shmget(0x5005, sizeof(stgirl), 0640|IPC_CREAT);if ( shmid ==-1 ){ cout << "shmget(0x5005) failed.\n"; return -1; }cout << "shmid=" << shmid << endl;// 第2步:把共享内存连接到当前进程的地址空间。stgirl *ptr=(stgirl *)shmat(shmid,0,0);if ( ptr==(void *)-1 ){ cout << "shmat() failed\n"; return -1; }// 创建、初始化二元信号量。csemp mutex;if (mutex.init(0x5005)==false){cout << "mutex.init(0x5005) failed.\n"; return -1;}cout << "申请加锁...\n";mutex.wait(); // 申请加锁。cout << "申请加锁成功。\n";// 第3步:使用共享内存,对共享内存进行读/写。cout << "原值:no=" << ptr->no << ",name=" << ptr->name << endl;  // 显示共享内存中的原值。ptr->no=atoi(argv[1]);        // 对超女结构体的no成员赋值。strcpy(ptr->name,argv[2]);    // 对超女结构体的name成员赋值。cout << "新值:no=" << ptr->no << ",name=" << ptr->name << endl;  // 显示共享内存中的当前值。sleep(10);mutex.post(); // 解锁。cout << "解锁。\n";// 查看信号量  :ipcs -s    // 删除信号量  :ipcrm sem 信号量id// 查看共享内存:ipcs -m    // 删除共享内存:ipcrm -m  共享内存id// 第4步:把共享内存从当前进程中分离。shmdt(ptr);// 第5步:删除共享内存。if (shmctl(shmid,IPC_RMID,0)==-1){ cout << "shmctl failed\n"; return -1; }
}

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

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

相关文章

Webpack性能调优:从加载器到插件的全面优化

Webpack 是一个模块打包工具&#xff0c;它将项目中的各种资源&#xff08;JavaScript、CSS、图片等&#xff09;转换成一个或多个浏览器可识别的输出文件。优化 Webpack 的性能主要涉及减少构建时间、减小输出文件大小和提高应用加载速度。 2500G计算机入门到高级架构师开发资…

解析Spring Bean对象的作用域机制

1. 作用域范围 1. singleton单例&#xff1a;在整个SpringBoot应用中&#xff0c;只创建bean的一个实例; 2. propotye多例&#xff1a;每次注入或者通过Spring应用上下文获取的时候&#xff0c;都会创建一个新的bean实例; 3. request请求&#xff1a;一次http请求&#xff0c;…

C++240527

定义自己的命名空间 my_sapce&#xff0c;在 my_sapce 中定义 string 类型的变量 s1&#xff0c;再 定义一个函数 完成 对字符串的逆置 。 #include <iostream>//导入 标准命名空间&#xff0c;cout 和 endl 标识符 存在于标准命名空间中 using namespace std;//定义了自…

springboot+vue+mybatis基于java web的公益网站的设计与实现+jsp+PPT+论文+讲解+售后

现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&#xff0c;集中处理数据信息的管理方式。本公益网站就是在这样的大环境下诞生&#xff0c;其可以帮助管理者在短时间内处理完毕庞大的数据信息&#xff0c;使…

AJ-Report一次排错处理

山重水复疑无路&#xff0c;柳暗花明又一村...... 新项目需要选型开源的AJ-Report&#xff0c;计划再次基础上进行二开。 官网地址&#xff1a; AJ-Report: AJ-Report是一个完全开源&#xff0c;拖拽编辑的可视化设计工具。三步快速完成大屏&#xff1a;配置数据源---->写…

力扣503. 下一个更大元素 II

Problem: 503. 下一个更大元素 II 文章目录 题目描述思路复杂度Code 题目描述 思路 由于此题是环形数组&#xff0c;我们在利用单调栈模板的基础上还需要将给定数组扩大一倍&#xff0c;但实际上我们只需要利用取余的操作模拟扩大数组即可&#xff08;具体操作看代码。在解决有…

Spring Boot集成shiro之使用redis缓存demo

1.背景 上次发了这篇文章《Spring Boot集成Shiro快速入门Demo》后&#xff0c;有网友“just.blue”后台反馈集成redis有点问题&#xff0c;今天特地把集成过程发出来 2.为什么要使用cache 用来减轻数据库的访问压力&#xff0c;从而提升查询效率。 3.Shiro使用Redis做缓存 …

【R语言】获取任意颜色的HTML 颜色代码、十六进制颜色代码、 RGB代码

网站来源&#xff1a; https://htmlcolorcodes.com/ 界面如下所示&#xff1a; 通过鼠标任意选择不同的颜色&#xff0c;就能获取该色的十六进制代码、RGB代码等。 除此之外&#xff0c;还提供了一些常用颜色的便捷选项,如下&#xff1a; 任意选择一种颜色&#xff0c;即可出…

会声会影2024旗舰版神器,让你的视频秒变大片,小白也能轻松上手

在数字时代&#xff0c;视频已经成为了人们表达自我、记录生活的重要方式。无论是旅行中的美景&#xff0c;还是生活中的点滴瞬间&#xff0c;我们都渴望能够用镜头捕捉下来&#xff0c;并通过精心剪辑&#xff0c;将这些美好的画面永远珍藏。然而&#xff0c;对于大多数人来说…

【spring boot+Lazy ORM+mysql】开发一个数据库管理系统实现对应数据库数据查看和修改

【spring bootLazy ORMmysql】开发一个数据库管理系统实现对应数据库数据查看和修改 演示项目地址&#xff1a;http://124.222.48.62:30193/wu-smart-acw-ui/index.html#/login &#xff08;admin/admin&#xff09; 功能 用户登录注册新增、编辑数实例新增、编辑数据库信息…

[论文笔记]SELF-INSTRUCT

引言 今天带来论文SELF-INSTRUCT: Aligning Language Models with Self-Generated Instructions的笔记。 大型指令微调的语言模型(被微调以响应指令)展示了在新任务上零样本泛化的显著能力。然而&#xff0c;它们严重依赖于人工编写的指令数据&#xff0c;这种数据在数量、多…

element ui 的el-input输入一个字后失去焦点,需重新点击输入框才能再次输入

解决方案&#xff1a; 我是form表单嵌套表格&#xff0c;里面的el-input输入框&#xff0c;输入第一个值的时候会突然失去焦点&#xff0c;需要再次点击输入框才能正常输入&#xff0c;原因是table的key值&#xff0c;需要改成正常的index即可&#xff0c;如果你是循环的&…

服务器重装系统与磁盘操作

诱因&#xff1a;服务器原来装的EXSI&#xff0c;现在要重装一个ubuntu server&#xff0c;出现了下面一些问题&#xff0c;在此记录一下。 目录 1、过程中出现的问题&#xff08;2024.5.26&#xff09;1.1 问题1&#xff1a;如何磨掉原来的ESXI&#xff1f;1.2 问题2&#xf…

赶紧收藏!2024 年最常见 20道 Redis面试题(九)

上一篇地址&#xff1a;赶紧收藏&#xff01;2024 年最常见 20道 Redis面试题&#xff08;八&#xff09;-CSDN博客 十七、如何使用Redis做异步队列&#xff1f; 使用 Redis 作为异步队列主要依赖于 Redis 的列表&#xff08;list&#xff09;数据结构&#xff0c;列表提供了…

webserver服务器从零搭建到上线(五)|noncopyable类和Logger类

文章目录 noncopyable类delete掉了拷贝构造和析构protected成员1. 允许派生2.防止直接实例化 主要使用场景 Logger类定义日志级别输出一个日志类实现对应的成员函数实现宏函数来调用日志类 知识拓展 noncopyable类 我们首先进入/muduo/net中查看TcpServer.h、EventLoop.h等等核…

可以免费测试的身份证实名认证接口-C#调用示例

在数字时代的浪潮中&#xff0c;每秒都在上演着信息的急速交互。但在这份高效背后&#xff0c;如何确保每一次交易、登录的安全与真实性&#xff0c;成为了困扰线上平台的一大难题。翔云身份证实名认证接口的出现&#xff0c;正是您稳固防线&#xff0c;提升用户体验的得力助手…

信号量和事件及队列补充

【一】信号量(了解&#xff09; 信号量Semahpore&#xff08;同线程一样&#xff09; 【1】引入 互斥锁 同时只允许一个线程更改数据&#xff0c;而Semaphore是同时允许一定数量的线程更改数据 【2】例子 比如厕所有3个坑&#xff0c;那最多只允许3个人上厕所&#xff0c;后…

死锁和递归锁

【一】死锁 【1】介绍 死锁是指两个或多个进程&#xff0c;在执行过程中&#xff0c;因争夺资源而造成了互相等待的现象 即两个或多个进程持有各自的锁并视图获取对方持有的锁&#xff0c;从而导致阻塞&#xff0c;不能继续执行&#xff0c;一直僵在这 这种情况下&#xff0…

LeetCode - 贪心算法 (Greedy Algorithm) 集合 [分配问题、区间问题]

欢迎关注我的CSDN&#xff1a;https://spike.blog.csdn.net/ 本文地址&#xff1a;https://spike.blog.csdn.net/article/details/139242199 贪心算法&#xff0c;是在每一步选择中&#xff0c;都采取当前状态下&#xff0c;最好或最优&#xff08;即最有利&#xff09;的选择&…

Linux相关知识

一.Linux是什么? 1.Linux是一款开源免费的操作系统 目前市面上较知名的发行版有:Ubuntu,ReaHat,Centos,Debain… ​ 2.Linux的优势? ​ ①性能强劲,安全稳定 ​ ②可定制 ​ ③硬件配置要求低 ​ ④嵌入移动设备 二.Linux安装 三.文件和目录结构 /bin 常用命令 /sbin root…