【探索Linux】—— 强大的命令行工具 P.22(POSIX信号量)

在这里插入图片描述

阅读导航

  • 引言
  • 一、POSIX信号量的基本概念
  • 二、信号量的相关操作
    • 1 . 初始化信号量sem_init ( )
      • (1)原型
      • (2)参数
      • (3)返回值
      • (4)示例代码
    • 2 . 等待信号量
      • (1)sem_wait ( )
        • - 原型
        • - 参数
        • - 返回值
      • (2)sem_trywait ( )
        • - 原型
        • - 参数
        • - 返回值
      • (3)sem_timedwait
        • - 原型
        • - 参数
        • - 返回值
      • (4)示例代码
    • 3 . 发布信号量sem_post( )
      • (1)原型
      • (2)参数
      • (3)返回值
      • (4)示例代码
      • 🚨 注意事项
    • 4 . 销毁信号量sem_destroy()
      • (1)原型
      • (2)参数
      • (3)返回值
      • (4)示例代码
  • 三、 使用场景与注意事项
  • 温馨提示

引言

在上一篇文章中,我们深入探讨了多线程编程的核心概念,包括线程同步、条件变量以及线程安全等关键技术,为读者揭示了并发编程的复杂性及其解决方案。这些概念和技术是实现高效、稳定并发应用程序的基础。继续在并发编程的旅途上前进,本篇文章将引导我们走进Linux操作系统下的另一个重要概念——POSIX信号量(Semaphore)。

POSIX信号量是一种用于进程或线程间同步的机制,它提供了一种控制资源访问的方法,确保在任何时刻只有特定数量的线程可以访问特定的资源。这在处理资源共享问题时尤其重要,比如,在操作系统、数据库管理系统等领域,正确的使用信号量可以有效避免死锁和竞态条件,保证系统的稳定运行。

随着并发编程的普及,掌握各种同步机制成为每位开发者的必备技能。POSIX信号量作为其中的重要组成部分,其重要性不言而喻。让我们一起深入探索POSIX信号量,解锁并发编程的新技能。

一、POSIX信号量的基本概念

POSIX信号量是一种在POSIX-compliant系统(如Linux)中实现的线程或进程间同步机制。它提供了一组标准化的API,用于控制对共享资源的访问。信号量本质上是一个计数器,用于表示可用资源的数量。它支持两个基本操作:等待wait)和信号signal),在不同的文献中,这两个操作也被称为P(Proberen,尝试)和V(Verhogen,增加)操作。

二、信号量的相关操作

POSIX信号量定义在<semaphore.h>头文件中,主要包括以下几个函数

1 . 初始化信号量sem_init ( )

初始化:在使用信号量之前,必须先对其进行初始化,设定信号量的初始值,即可用资源的数量。

(1)原型

int sem_init(sem_t *sem, int pshared, unsigned int value);

(2)参数

  • sem指向信号量对象的指针
  • pshared此参数指示信号量是在进程间共享还是仅限于线程间的共享。如果pshared的值为0,则信号量仅在同一进程的线程间共享;如果pshared的值非0,则信号量可以在多个进程间共享
  • value用于指定信号量的初始值

(3)返回值

  • 成功时返回0
  • 失败时返回-1,并设置errno以指示错误原因

(4)示例代码

下面是一个使用sem_init初始化信号量的简单示例:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>sem_t sem;void* thread_function(void* arg) {// 等待信号量sem_wait(&sem);printf("Entered..\n");// 临界区代码...printf("Exiting..\n");// 释放信号量sem_post(&sem);
}int main() {// 初始化信号量,初始值设为1if (sem_init(&sem, 0, 1) != 0) {perror("sem_init");return 1;}pthread_t t1, t2;// 创建两个线程pthread_create(&t1, NULL, thread_function, NULL);pthread_create(&t2, NULL, thread_function, NULL);// 等待线程结束pthread_join(t1, NULL);pthread_join(t2, NULL);// 销毁信号量sem_destroy(&sem);return 0;
}

在这个示例中,我们创建了一个初始值为1的信号量,这意味着它可以被一个线程获取,从而进入临界区。当信号量的值为0时,其他试图获取该信号量的线程将会阻塞,直到信号量的值再次变为正数。通过调用sem_waitsem_post函数,线程在进入和退出临界区时分别等待和释放信号量,从而实现了线程间的同步。

2 . 等待信号量

等待(P操作):当线程尝试获取一个资源时,会执行等待操作。如果信号量的值大于0,表示有资源可用,它就会减1并继续执行。如果信号量的值为0,表示没有可用资源,执行等待操作的线程将被阻塞,直到信号量的值变为大于0

(1)sem_wait ( )

sem_wait 函数用于等待信号量。如果信号量的值大于0,该函数会将它减1并立即返回,让调用线程继续执行。如果信号量的值为0,调用线程将阻塞,直到信号量的值变为大于0

- 原型
int sem_wait(sem_t *sem);
- 参数
  • sem:指向信号量对象的指针。
- 返回值
  • 成功时返回0
  • 失败时返回-1,并设置errno以指示错误原因

(2)sem_trywait ( )

sem_trywait 函数尝试等待信号量,但与sem_wait不同的是,如果信号量的值为0,sem_trywait不会阻塞调用线程,而是立即返回一个错误。

- 原型
int sem_trywait(sem_t *sem);
- 参数
  • sem:指向信号量对象的指针。
- 返回值
  • 成功时返回0。
  • 如果信号量的值为0,则返回-1,并设置errno为EAGAIN,表示没有获取到信号量。

(3)sem_timedwait

sem_timedwait 函数也用于等待信号量,但它允许指定一个超时时间。如果在指定的时间内信号量没有变为可用状态,函数将返回一个错误。

- 原型
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);
- 参数
  • sem:指向信号量对象的指针。
  • abs_timeout:指向timespec结构的指针,该结构指定了一个绝对超时时间。这个时间是从Epoch(1970-01-01 00:00:00 UTC)开始计算的。
- 返回值
  • 成功时返回0。
  • 如果在指定时间内未能获取信号量,则返回-1,并设置errno为ETIMEDOUT。

(4)示例代码

下面是一个使用sem_wait等待信号量的简单示例:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>sem_t sem;void* thread_function(void* arg) {// 等待信号量sem_wait(&sem);printf("Entered..\n");// 模拟临界区代码sleep(1);printf("Exiting..\n");// 释放信号量sem_post(&sem);
}int main() {// 初始化信号量,初始值设为1if (sem_init(&sem, 0, 1) != 0) {perror("sem_init");return 1;}pthread_t t1, t2;// 创建两个线程pthread_create(&t1, NULL, thread_function, NULL);pthread_create(&t2, NULL, thread_function, NULL);// 等待线程结束pthread_join(t1, NULL);pthread_join(t2, NULL);// 销毁信号量sem_destroy(&sem);return 0;
}

在这个示例中,sem_wait被用于确保在任何时刻只有一个线程可以进入临界区执行。这是通过在进入临界区之前调用sem_wait来实现的,它会等待信号量变为可用(即信号量的值大于0)。成功进入临界区的线程在离开时通过调用sem_post来增加信号量的值,从而可能允许其他等待中的线程进入临界区。

3 . 发布信号量sem_post( )

信号(V操作)当线程释放一个资源时,会执行信号操作。信号操作会将信号量的值加1。如果有其他线程因等待该信号量而被阻塞,其中一个线程将被唤醒,以便它可以获取资源

sem_post 函数用于增加信号量的值。当信号量的值从0变为正数时,如果有线程因调用 sem_wait 而阻塞在该信号量上,那么其中一个线程将被唤醒(即解除阻塞状态并获得信号量)。

(1)原型

int sem_post(sem_t *sem);

(2)参数

  • sem:指向信号量对象的指针。

(3)返回值

  • 成功时返回0。
  • 失败时返回-1,并设置errno以指示错误原因。

(4)示例代码

以下是一个使用 sem_post 来发布(释放)信号量的简单示例,它演示了如何在一个线程中使用 sem_post 来允许另一个线程继续执行。

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>sem_t sem;void* thread_function(void* arg) {printf("Thread waiting for the semaphore...\n");sem_wait(&sem); // 等待信号量printf("Semaphore acquired by thread.\n");// 执行一些操作...sleep(1); // 模拟耗时操作printf("Thread releasing the semaphore.\n");sem_post(&sem); // 释放信号量
}int main() {// 初始化信号量,初始值设为0if (sem_init(&sem, 0, 0) != 0) {perror("sem_init failed");return 1;}pthread_t t1;pthread_create(&t1, NULL, thread_function, NULL);printf("Main thread sleeping for 2 seconds...\n");sleep(2); // 让线程有足够的时间进入等待状态printf("Main thread posting the semaphore.\n");sem_post(&sem); // 主线程释放信号量,允许子线程继续执行pthread_join(t1, NULL); // 等待子线程结束sem_destroy(&sem); // 销毁信号量return 0;
}

在这个示例中,主线程初始化一个信号量并创建一个工作线程,然后休眠2秒钟。工作线程启动后会尝试通过调用 sem_wait 获取信号量,但由于信号量的初始值被设置为0,所以它将被阻塞。主线程在休眠结束后通过调用 sem_post 增加信号量的值,这导致阻塞的工作线程被唤醒并继续执行。

🚨 注意事项

  • 使用 sem_post 时,应确保信号量已经通过 sem_init 或其他方式正确初始化
  • sem_post 可以在任何时候被调用,不仅限于信号量的持有者线程。这意味着,即使一个线程没有通过 sem_wait 获取信号量,它也可以调用 sem_post 来增加信号量的值。
  • 在多线程程序中合理使用信号量,可以有效地控制线程间的同步和互斥,但需要注意避免死锁和竞争条件

4 . 销毁信号量sem_destroy()

在使用POSIX信号量进行线程同步和互斥操作后,正确地销毁信号量是非常重要的。这不仅有助于释放系统资源,还可以避免资源泄漏。POSIX提供了sem_destroy函数来销毁信号量。

sem_destroy函数用于销毁信号量,释放与之关联的资源。一旦信号量被销毁,它就不能再被使用,除非再次被初始化。

(1)原型

int sem_destroy(sem_t *sem);

(2)参数

  • sem:指向信号量对象的指针。

(3)返回值

  • 成功时返回0。
  • 失败时返回-1,并设置errno以指示错误原因。

(4)示例代码

以下是一个简单的示例,演示了如何创建、使用和销毁一个信号量:

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>sem_t sem;void* thread_function(void* arg) {sem_wait(&sem); // 等待信号量printf("Thread entered critical section.\n");// 执行临界区代码...sleep(1); // 模拟耗时操作printf("Thread leaving critical section.\n");sem_post(&sem); // 释放信号量
}int main() {// 初始化信号量,初始值设为1if (sem_init(&sem, 0, 1) != 0) {perror("sem_init failed");return 1;}pthread_t t1, t2;pthread_create(&t1, NULL, thread_function, NULL);pthread_create(&t2, NULL, thread_function, NULL);// 等待线程完成pthread_join(t1, NULL);pthread_join(t2, NULL);// 销毁信号量if (sem_destroy(&sem) != 0) {perror("sem_destroy failed");}return 0;
}

在这个示例中,我们创建了一个初始值为1的信号量,允许两个线程依次进入临界区。在线程执行完毕后,我们通过调用sem_destroy来销毁信号量,以确保程序优雅地释放了所有分配的资源。

三、 使用场景与注意事项

POSIX信号量广泛用于多线程和多进程环境中,以实现对共享资源的同步访问控制。在设计并发程序时,正确使用信号量是保证数据一致性和系统稳定性的关键。

在使用POSIX信号量时,需要注意以下几点:

  • 确保在适当的时候初始化和销毁信号量。
  • 避免死锁,确保每个等待信号量的线程最终都能够继续执行。
  • 谨慎处理信号量操作可能失败的情况,特别是sem_wait可能由于中断而提前返回。

通过合理使用POSIX信号量,开发者可以有效地解决多线程编程中的同步和互斥问题,提高程序的稳定性和效率。

温馨提示

感谢您对博主文章的关注与支持!如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C++编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索Linux、C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

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

相关文章

面试浏览器框架八股文十问十答第一期

面试浏览器框架八股文十问十答第一期 作者&#xff1a;程序员小白条&#xff0c;个人博客 相信看了本文后&#xff0c;对你的面试是有一定帮助的&#xff01;关注专栏后就能收到持续更新&#xff01; ⭐点赞⭐收藏⭐不迷路&#xff01;⭐ 1&#xff09;什么是 XSS 攻击&#…

高程 | 类与对象(c++)

文章目录 &#x1f4da;面向对象程序设计的基本特点&#x1f407;抽象——概括问题&#xff0c;抽出公共性质并加以描述。&#x1f407;封装——将抽象所得数据和行为相结合&#xff0c;形成一个有机的整体&#xff0c;形成“类”。&#x1f407;继承——在原有类特性的基础上&…

Python爬虫学习

1.1搭建爬虫程序开发环境 爬取未来七天天气预报 from bs4 import BeautifulSoup from bs4 import UnicodeDammit import urllib.request url"http://www.weather.com.cn/weather/101120901.shtml" try:headers{"User-Agent":"Mozilla/5.0 (Windows …

C/C++进制转换

十进制转化为二进制 进制转化#include <iostream> using namespace std;void change(int); int main() {int num;cout << "请输入一个十进制数: ";cin >> num;cout << "转化后的二进制数为: ";change(num);return 0; } void chan…

51_蓝桥杯_蜂鸣器与继电器

一 电路 二 蜂鸣器与继电器工作原理 2.1蜂鸣器与继电器 2.2 十六进制与二进制 二进制 0000 0001 0010 0011 0100 0101 0110 0111 1000 1001 1010 1011 1100 1101 1110 1111 十六进制 0 1 2 3 4 5 6 7 8 9 A B C D E F 2.3非门 二 代码 …

straight insertion sorting

直接插入排序算法&#xff1a;第一个元素作为有序序列&#xff0c;从第二个元素开始&#xff0c;在其之前的序列里找到对应的位置&#xff0c;然后插入。 下面用了tmp作为哨兵。 #include <iostream> using namespace std;const int Nn 100010; int a[Nn];int main() …

[嵌入式系统-26]:RT-Thread -13- 内核组件编程接口 - 文件系统

目录 一、RT-Thread文件系统 1.1 概述 1.2 如何配置文件系统 1.3 支持哪些文件系统 1.4 不同文件系统的比较 二、文件系统编程接口 2.1 概述 2.2 两种文件系统接口的比较 三、POSIX 文件操作函数 3.1 概述 3.2 代码实例 一、RT-Thread文件系统 1.1 概述 RT-Thread…

Java Lambda表达式:简化编程,提高效率

Java Lambda表达式&#xff1a;简化编程&#xff0c;提高效率 1. 使用Lambda表达式进行集合遍历1.1 未使用Lambda表达式&#xff1a;1.2 使用Lambda表达式&#xff1a; 2. 使用Lambda表达式进行排序2.1 未使用Lambda表达式&#xff1a;2.2 使用Lambda表达式&#xff1a; 3. 使用…

精通C语言:打造高效便捷的通讯录管理系统

✨✨ 欢迎大家来到贝蒂大讲堂✨✨ &#x1f388;&#x1f388;养成好习惯&#xff0c;先赞后看哦~&#x1f388;&#x1f388; 所属专栏&#xff1a;C语言项目 贝蒂的主页&#xff1a;Betty‘s blog 引言 在我们大致学习完C语言之后&#xff0c;我们就可以利用目前所学的知识去…

HCIA-HarmonyOS设备开发认证V2.0-内核扩展组件

目录 一、CPU 占用率1.1、CPU 占用率基本概念1.2、CPU 占用率运行机制1.3、CPU 占用率开发流程 二、动态加载2.1、 动态加载基本概念2.2、动态加载运行机制 坚持就有收获 一、CPU 占用率 1.1、CPU 占用率基本概念 CPU&#xff08;中央处理器&#xff0c;Central Processing U…

第三节作业:基于 InternLM 和 LangChain 搭建你的知识库

参考文档&#xff1a;https://github.com/InternLM/tutorial/tree/main/langchain 基础作业&#xff1a;复现课程知识库助手搭建过程 (截图) 1.环境配置 2.知识库搭建 &#xff08;1&#xff09;数据收集 收集由上海人工智能实验室开源的一系列大模型工具开源仓库作为语料库来…

单html页面使用Vue3和Element-Plus

快速入门&#xff08;直接整案例&#xff0c;看显示效果&#xff09; 使用CDN方式引入Vue3使用CDN方式引入Element-Plus的样式文件和组件库 案例1&#xff08;按钮&#xff0c;表格&#xff09; <!DOCTYPE html> <html lang"en"> <head><meta …

什么是死锁以及如何避免死锁?

死锁是多线程或多进程编程中的一个常见问题&#xff0c;发生于两个或多个进程或线程在执行过程中&#xff0c;因为争夺资源而相互等待对方释放资源&#xff0c;导致它们之间形成了一个无尽的循环等待&#xff0c;从而无法继续执行的情况。在死锁状态下&#xff0c;所有相关的进…

人工智能时代

一、人工智能发展历史:从概念到现实 人工智能(Artificial Intelligence,简称AI)是计算机科学领域中一门旨在构建能够执行人类智能任务的系统的分支。其发展历程充满曲折,从概念的提出到如今的广泛应用,是技术、理论和实践相互交织的产物。 1. 起源(20世纪中期) 人工智…

对称密钥密码体制、公钥密码体制

1 对称密钥密码体制 加密密钥与解密密钥都使用相同密钥的密码体制。 数据加密标准 DES DES 属于对称密钥密码体制&#xff0c;是一种分组密码。 在加密前&#xff0c;先对整个明文进行分组。每一个组长为 64 位。 然后对每一个 64 位 二进制数据进行加密处理&#xff0c;…

预处理和预编译、预编译头文件stdafx.h和pch.h

预处理和预编译 预处理&#xff1a;发生在编译前&#xff0c;做些代码文本的替换工作。 宏定义&#xff1a;#define宏定义的替换。文件包含&#xff1a;#include指明的源程序文件嵌入到当前源文件的指令位置处。条件编译&#xff1a;根据标识符来判断程序段是否需要编译这段代…

云计算基础-华为存储实验

存储配置流程 创建硬盘域&#xff08;CK&#xff0c;包括热备CK&#xff09;创建存储池&#xff08;CKG、Extent&#xff09;创建LUN、LUN组、将LUN加入LUN组创建主机、主机组、将主机加入主机组创建映射视图&#xff08;LUN组和主机组的映射&#xff09; 1. 创建CK 创建硬盘…

MyBatis sql拦截器实现一个自动根据租户进行分表的方案

需求描述&#xff1a; 在一个多租户系统中&#xff0c;通过 MyBatis 实现动态数据表分离。具体来说&#xff0c;您希望通过 MyBatis 拦截器在执行 SQL 时自动将表名根据当前租户 ID (tenantId) 进行修改。这样&#xff0c;每个租户的数据就可以存储在专属于它们的表中&#xf…

【数据结构】二叉查找树和平衡二叉树,以及二者的区别

目录 1、二叉查找树 1.1、定义 1.2、查找二叉树的优点 1.2、查找二叉树的弊端 2、平衡二叉树 2.1、定义 2.2、 实现树结构平衡的方法&#xff08;旋转机制&#xff09; 2.2.1、左旋 2.2.2、右旋 3、总结 1、二叉查找树 二叉查找树又名二叉排序树&#xff0c;亦称二叉搜…

究极小白如何自己搭建一个自动发卡网站-独角数卡

本人从来没接触过建站&#xff0c;我之前都是在TB上花90叫别人给我搭建的网站&#xff0c;前几天这个TB店倒闭跑路了&#xff0c;而我的发卡网也打不开了&#xff0c;没办法&#xff0c;逼上梁山&#xff0c;自己捣鼓出来了&#xff01;下面是2023/4/2自己建好的&#xff01; …