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

news2024/11/23 19:12:41

在这里插入图片描述

阅读导航

  • 引言
  • 一、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() {
    // 初始化信号量,初始值设为1
    if (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() {
    // 初始化信号量,初始值设为1
    if (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() {
    // 初始化信号量,初始值设为0
    if (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() {
    // 初始化信号量,初始值设为1
    if (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.coloradmin.cn/o/1452927.html

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

相关文章

OpenCV库及在ROS中使用

OpenCV库及在ROS中使用 依赖 cv_bridge image_transport roscpp rospy sensor_msgs std_msgsCMakeLists.txt添加 find_package(OpenCV REQUIRED) include_directories(${OpenCV_INCLUDE_DIRS}) target_link_libraries(pub_img_topic ${catkin_LIBRARIES} ${Opencv_LIBS}) C …

【PyQt】14-绘图-QPainter

文章目录 前言一、QPainter二、绘制文本-drawTextQt里面的文本对齐方式 运行结果 三、像素点总结 前言 1、学会画图方法 一、QPainter 通常可以绘制文本、各种图形&#xff08;点、线、椭圆、弧、扇形、多边形等等&#xff09;、图像。 必须在painrEvent事件方法中绘制各种元…

win32汇编获取系统信息

.data fmt db "页尺寸&#xff1a;%d",0 db "" lpsystem SYSTEM_INFO <?> szbuf db 200 dup(0) .const szCaption db 系统信息,0 .code start: invoke GetSystemInfo,addr lpsystem …

php 函数(方法)、日期函数

php 函数、日期函数 1. php函数2. 日期函数 1. php函数 <?php// 创建一个函数 function hello($who) {echo $who.Hello World!; }hello("老张");给参数一个默认值&#xff0c;当然自己有变量走自己的 2. 日期函数 <?php/** date(Y-m-d H:i:s)返回的时间是…

anaconda安装路径默认在D盘,但安装环境的envs路径跑到C盘,修改为D盘

安装的anaconda环境&#xff0c;路径是在anaconda安装目录下的envs中&#xff08;D:\APPFile\Anaconda3\envs&#xff09;&#xff0c;然而&#xff0c;这次创建的却是在 C:\Users\xxx.conda\envs 中。 首先&#xff0c;找到用户目录下的.condarc文件&#xff08;C:\Users\use…

MOSFET栅极应用电路分析汇总(驱动、加速、保护、自举等等)

概述 MOSFET是一种常见的电压型控制器件&#xff0c;具有开关速度快、高频性能、输入阻抗高、噪声小、驱动功率小、动态范围大、安全工作区域(SOA)宽等一系列的优点&#xff0c;因此被广泛的应用于开关电源、电机控制、电动工具等各行各业。栅极做为MOSFET本身较薄弱的环节&am…

Android安卓架构MVC、MVP、MVVM模式的概念与区别

目录 MVC框架 MVP框架 MVVM框架 MVVM与MVP区别 MVVM与MVC区别 MVC、MVP、MVVM模式哪个要好一些 MVC&#xff08;Model-View-Controller&#xff09;、MVP&#xff08;Model-View-Presenter&#xff09;、MVVM&#xff08;Model-View-ViewModel&#xff09;是三种常见的软…

数据库第五次实验

目录 1 创建数据表 2 创建多个用户 ​​​​​​​3 用户的授权 ​​​​​​​4 用户权限的回收 ​​​​​​​5 角色的创建与授权 ​​​​​​​6 回收角色的权利 ​​​​​​​7 审计的设置 1 创建数据表 SQL语句&#xff1a; use experimentfive; create table…

游戏安全组件运行时发生异常1-0-0

可能是这个服务&#xff0c;可能被禁用了。 如果是文件缺少直接修复游戏

鸿蒙应用开发工程师招聘多吗?工资有多少呢?

随着鸿蒙操作系统的快速普及&#xff0c;越来越多的企业开始重视鸿蒙应用开发人才的培养和引进。那么&#xff0c;目前市场上鸿蒙应用开发工程师招聘多吗&#xff1f;工资有多少呢&#xff1f; 首先&#xff0c;我们来了解一下鸿蒙应用开发工程师的招聘情况。随着鸿蒙操作系统…

【头歌·计组·自己动手画CPU】四、控制器设计(理论版) 【计算机硬件系统设计】

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎 ❤️关注 &#x1f44d;点赞 &#x1f64c;收藏 ✍️留言 文章目录 一、课程设计目的二、课程设计内容三、课程设计步骤四、课程设计总结 一、课程设计目的 掌握 CPU …

每日一题 429.N叉树的层序遍历

429. N 叉树的层序遍历 描述&#xff1a; 给定一个 N 叉树&#xff0c;返回其节点值的层序遍历。&#xff08;即从左到右&#xff0c;逐层遍历&#xff09;。 树的序列化输入是用层序遍历&#xff0c;每组子节点都由 null 值分隔&#xff08;参见示例&#xff09;。 示例 1…

Jetson下的i2c、spi、gpio、can、uart、485代码示例使用说明

适用于刚接触新设备的同学参考。 UART 演示代码 目前&#xff0c;这是一个简单的 Python 3 脚本&#xff0c;用于与 NVIDIA Jetson Nano 开发套件的 J41 头上的 UART 进行交互。当然只要是jetson都可以用&#xff0c;改一下程序里面的设备节点就行 JetsonHacks 上的原始文章&…

一起学量化之Aroon指标

Aroon指标是由Tushar Chande于1995年开发的技术分析工具,旨在识别股票是否处于趋势中及趋势的强度。它通过分析股票价格在一定周期内创下的新高和新低来预测趋势的变化,这基于一种观念:强势趋势通常伴随着频繁的新高或新低。 1. Aroon指标的组成 Aroon指标由两个部分组成:…

Manifest merger failed with multiple errors, see logs

问题 Manifest merger failed with multiple errors, see logs详细问题 笔者进行Android 项目开发&#xff0c;修改AndroidManifest.xml代码后&#xff0c;控制台报错 AndroidManifest.xml报错核心代码 <manifest><uses-permission android:name"android.perm…

问题:在额定电压500V以下的电路中,使用的各种用电设备,一般称为(_ _ _)用电设备 #媒体#媒体#媒体

问题&#xff1a;在额定电压500V以下的电路中,使用的各种用电设备,一般称为&#xff08;_ _ _)用电设备 参考答案如图所示

鸿蒙开发系列教程(二十三)--List 列表操作(2)

列表样式 1、设置内容间距 在列表项之间添加间距&#xff0c;可以使用space参数&#xff0c;主轴方向 List({ space: 10 }) { … } 2、添加分隔线 分隔线用来将界面元素隔开&#xff0c;使单个元素更加容易识别。 startMargin和endMargin属性分别用于设置分隔线距离列表侧…

Unity类银河恶魔城学习记录7-7 P73 Setting sword type源代码

Alex教程每一P的教程原代码加上我自己的理解初步理解写的注释&#xff0c;可供学习Alex教程的人参考 此代码仅为较上一P有所改变的代码 【Unity教程】从0编程制作类银河恶魔城游戏_哔哩哔哩_bilibili Sword_Skill_Controller.cs using System.Collections; using System.Col…

C#开源免费的Windows右键菜单管理工具

前言 今天分享一个C#开源、免费、纯粹的Windows右键菜单管理工具&#xff1a;ContextMenuManager。 工具主要功能 程序支持国际化多语言显示。启用或禁用文件、文件夹、新建、发送到、打开方式、自定义文件格式、IE浏览器、WinX等右键菜单项目。对上述场景右键菜单项目进行修…

Springboot 配置使用 Elasticsearch

一、安装Elasticsearch 1、Windows安装 Windows安装比较简单&#xff0c;ES官网Download Elasticsearch | Elastic下载压缩包&#xff0c;解压出来&#xff0c; bin 目录下有个elasticsearch.bat&#xff0c;双击&#xff0c;就运行起来了。 然后在浏览器输入localhost:9200…