Linux中用于线程/进程同步的核心函数——`sem_wait`函数

<摘要>

sem_wait 是 POSIX 信号量操作函数,用于对信号量执行 P 操作(等待、获取)。它的核心功能是原子地将信号量的值减 1。如果信号量的值大于 0,则减 1 并立即返回;如果信号量的值为 0,则调用线程(或进程)会被阻塞,直到另一个线程执行 sem_post 增加信号量值后将其唤醒,或者被信号中断。它主要用于保护共享资源(实现互斥锁)或协调线程/进程间的执行顺序(同步),是构建并发程序的基石之一。


<解析>

你可以把信号量想象成一个令牌桶,而 sem_wait 就是获取令牌的操作。

  • 桶里有令牌(信号量值 > 0):你拿走一个,继续工作。
  • 桶里没令牌(信号量值 = 0):你必须等待,直到有人往桶里还回令牌(sem_post),你才能拿走一个并继续。
1) 函数的概念与用途
  • 功能:原子地减少(锁定)一个信号量的值。如果该操作会导致信号量值为负,则阻塞调用者。
  • 场景
    1. 互斥(Mutex):初始化信号量为 1。线程在访问临界区(共享资源)前调用 sem_wait,离开后调用 sem_post。这确保了任何时候只有一个线程在临界区内。
    2. 同步(Sync):初始化信号量为 0。用于协调线程间的执行顺序。例如,线程 A 必须等待线程 B 完成某项工作后才能继续,那么线程 A 会 sem_wait 在一个信号量上,而线程 B 完成后调用 sem_post 来唤醒 A。
    3. 控制资源访问数量:初始化信号量为 N(如数据库连接池大小)。线程访问资源前 sem_wait,用完后再 sem_post,从而将并发访问数控制在 N 以内。
2) 函数的声明与出处

sem_wait 定义在 <semaphore.h> 头文件中,是 POSIX 线程库的一部分,链接时需要 -pthread 选项。

int sem_wait(sem_t *sem);
3) 返回值的含义与取值范围
  • 成功:返回 0
  • 失败:返回 -1,并设置相应的错误码 errno
    • EINVAL:参数 sem 不是有效的信号量指针。
    • EINTR此调用被信号中断。这是一个非常重要且常见的情况。阻塞中的 sem_wait 可以被信号处理函数打断,此时它会返回 -1 并设置 errnoEINTR。健壮的程序需要检查并处理这种情况。
4) 参数的含义与取值范围
  1. sem_t *sem
    • 作用:指向一个已初始化信号量的指针。
    • 取值范围:必须是一个由 sem_initsem_open 初始化/创建的有效信号量对象的地址。
5) 函数使用案例

示例 1:用信号量实现互斥锁(保护共享变量)
此示例展示两个线程如何通过信号量安全地增加一个共享计数器。

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>#define NUM_OPERATIONS 100000int shared_counter = 0;
sem_t counter_sem; // 信号量,用于保护 shared_countervoid* increment_counter(void* arg) {for (int i = 0; i < NUM_OPERATIONS; ++i) {// 进入临界区前获取信号量 (P操作)if (sem_wait(&counter_sem) != 0) {perror("sem_wait failed");return NULL;}// 临界区开始shared_counter++; // 这是一个非原子操作,需要保护// 临界区结束// 离开临界区后释放信号量 (V操作)if (sem_post(&counter_sem) != 0) {perror("sem_post failed");return NULL;}}return NULL;
}int main() {pthread_t thread1, thread2;// 初始化一个用于互斥的信号量,初始值为 1if (sem_init(&counter_sem, 0, 1) != 0) {perror("sem_init failed");return 1;}// 创建两个线程if (pthread_create(&thread1, NULL, increment_counter, NULL) != 0 ||pthread_create(&thread2, NULL, increment_counter, NULL) != 0) {perror("pthread_create failed");return 1;}// 等待两个线程结束pthread_join(thread1, NULL);pthread_join(thread2, NULL);// 销毁信号量sem_destroy(&counter_sem);// 理论上最终结果应该是 2 * NUM_OPERATIONSprintf("Expected final value: %d\n", 2 * NUM_OPERATIONS);printf("Actual final value: %d\n", shared_counter);// 如果没有信号量保护,实际值通常会小于预期值 due to race conditionsreturn 0;
}

示例 2:用信号量实现线程同步(生产者-消费者模型)
此示例展示一个简单的单生产者-单消费者模型,使用两个信号量来同步生产和消费的顺序。

#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>#define BUFFER_SIZE 5int buffer[BUFFER_SIZE];
int in = 0, out = 0;sem_t empty_slots; // 计数空槽位的信号量
sem_t full_slots;  // 计数已填充槽位的信号量void* producer(void* arg) {int item = 0;for (int i = 0; i < 10; ++i) {item = i; // 生产一个物品// 等待一个空槽位 (P(empty))sem_wait(&empty_slots);// 临界区:将物品放入缓冲区buffer[in] = item;printf("Produced: %d at index %d\n", item, in);in = (in + 1) % BUFFER_SIZE;// 临界区结束// 通知消费者多了一个满槽位 (V(full))sem_post(&full_slots);// 模拟生产时间sleep(1);}return NULL;
}void* consumer(void* arg) {int item;for (int i = 0; i < 10; ++i) {// 等待一个满槽位 (P(full))sem_wait(&full_slots);// 临界区:从缓冲区取出物品item = buffer[out];printf("Consumed: %d from index %d\n", item, out);out = (out + 1) % BUFFER_SIZE;// 临界区结束// 通知生产者多了一个空槽位 (V(empty))sem_post(&empty_slots);// 模拟消费处理时间sleep(2);}return NULL;
}int main() {pthread_t prod_thread, cons_thread;// 初始化信号量// 开始时所有槽位都是空的sem_init(&empty_slots, 0, BUFFER_SIZE);// 开始时没有已填充的槽位sem_init(&full_slots, 0, 0);pthread_create(&prod_thread, NULL, producer, NULL);pthread_create(&cons_thread, NULL, consumer, NULL);pthread_join(prod_thread, NULL);pthread_join(cons_thread, NULL);sem_destroy(&empty_slots);sem_destroy(&full_slots);printf("Producer-Consumer simulation finished.\n");return 0;
}

示例 3:处理 sem_wait 被信号中断(EINTR)
此示例展示如何编写健壮的代码来处理 sem_wait 被信号中断的情况。

#include <stdio.h>
#include <semaphore.h>
#include <signal.h>
#include <errno.h>sem_t demo_sem;void signal_handler(int sig) {printf("Signal %d received.\n", sig);// 信号处理函数不做复杂操作,只是打断阻塞调用
}int robust_sem_wait(sem_t *sem) {int ret;// 使用循环来重试被信号中断的 sem_waitwhile ((ret = sem_wait(sem)) == -1 && errno == EINTR) {// 如果失败原因是 EINTR,则继续重试printf("sem_wait was interrupted by a signal. Retrying...\n");continue;}return ret;
}int main() {// 设置信号处理函数 (例如 SIGINT: Ctrl+C)signal(SIGINT, signal_handler);// 初始化一个值为0的信号量,这样 sem_wait 会阻塞sem_init(&demo_sem, 0, 0);printf("Press Ctrl+C to interrupt the blocked sem_wait call.\n");printf("Calling sem_wait (will block)...\n");// 使用 robust_sem_wait 而不是直接的 sem_waitif (robust_sem_wait(&demo_sem) == 0) {printf("sem_wait succeeded!\n");} else {// 处理其他错误perror("robust_sem_wait failed with unexpected error");}sem_destroy(&demo_sem);return 0;
}
6) 编译方式与注意事项

编译命令(必须链接 pthread 库):

# 编译示例1和2
gcc -pthread -o sem_mutex sem_mutex.c
gcc -pthread -o sem_producer_consumer sem_producer_consumer.c
gcc -pthread -o sem_eintr sem_eintr.c

注意事项:

  1. 链接选项:使用 sem_* 系列函数时,必须在编译时加上 -pthread 链接选项,否则可能链接失败或产生不可预知的行为。
  2. 初始化:必须在使用信号量之前对其进行初始化(sem_init 用于进程内线程间,sem_open 用于进程间)。
  3. EINTR 处理阻塞的 sem_wait 可以被信号中断。编写健壮的程序时必须考虑这种情况,通常使用循环来重试。示例 3 展示了最佳实践。
  4. 销毁与清理:动态初始化的信号量(sem_init)在使用完毕后应使用 sem_destroy 进行销毁以释放资源。
  5. 不可用于文件操作sem_t 对象是内存中的结构体,不能直接用于 read/write 等文件操作。命名信号量(sem_open)有持久化语义,但操作仍需通过专门的函数。
  6. 与互斥锁的区别:信号量更通用。互斥锁(pthread_mutex_t)可视为初始值为 1 的信号量,但互斥锁有所有权概念(必须由锁定的线程解锁),而信号量没有。
7) 执行结果说明
  • 示例1:运行后,最终打印的 Actual final value 会精确地等于 Expected final value(200000)。如果移除 sem_waitsem_post 调用,由于竞态条件,实际值通常会远小于预期值。这证明了信号量成功保护了共享变量。
  • 示例2:运行后,你会看到生产和消费交替进行的日志。由于生产者生产速度快于消费者,生产者最终会因缓冲区满而阻塞(sem_wait(&empty_slots)),等待消费者消费。这展示了信号量如何协调不同速度的线程。
  • 示例3:运行后,程序会阻塞在 robust_sem_wait 中。此时按下 Ctrl+C 发送 SIGINT 信号,你会看到信号处理函数打印信息,并且 sem_wait 调用被中断后重试的日志。程序会继续阻塞,直到你用其他方式(如另一个终端)无法增加信号量值。这演示了如何优雅地处理信号中断。
8) 图文总结:sem_wait 工作流程

在这里插入图片描述

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

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

相关文章

25高教社杯数模国赛【B题超高质量思路+问题分析】

注&#xff1a;本内容由”数模加油站“ 原创出品&#xff0c;虽无偿分享&#xff0c;但创作不易。 欢迎参考teach&#xff0c;但请勿抄袭、盗卖或商用。 B 题 碳化硅外延层厚度的确定碳化硅作为一种新兴的第三代半导体材料&#xff0c;以其优越的综合性能表现正在受到越来越多…

【Linux篇章】再续传输层协议UDP :从低可靠到极速传输的协议重生之路,揭秘无连接通信的二次进化密码!

&#x1f4cc;本篇摘要&#xff1a; 本篇将承接上次的UDP系列网络编程&#xff0c;来深入认识下UDP协议的结构&#xff0c;特性&#xff0c;底层原理&#xff0c;注意事项及应用场景&#xff01; &#x1f3e0;欢迎拜访&#x1f3e0;&#xff1a;点击进入博主主页 &#x1f4c…

《A Study of Probabilistic Password Models》(IEEE SP 2014)——论文阅读

提出更高效的密码评估工具&#xff0c;将统计语言建模技术引入密码建模&#xff0c;系统评估各类概率密码模型性能&#xff0c;打破PCFGw的 “最优模型” 认知。一、研究背景当前研究存在两大关键问题&#xff1a;一是主流的 “猜测数图” 计算成本极高&#xff0c;且难以覆盖强…

校园外卖点餐系统(代码+数据库+LW)

摘要 随着校园生活节奏的加快&#xff0c;学生对外卖的需求日益增长。然而&#xff0c;传统的外卖服务存在诸多不便&#xff0c;如配送时间长、菜品选择有限、信息更新不及时等。为解决这些问题&#xff0c;本研究开发了一款校园外卖点餐系统&#xff0c;采用前端 Vue、后端 S…

友思特案例 | 食品行业视觉检测案例集锦(三)

食品制造质量检测对保障消费者安全和产品质量稳定至关重要&#xff0c;覆盖原材料至成品全阶段&#xff0c;含过程中检测与成品包装检测。近年人工智能深度学习及自动化系统正日益融入食品生产。本篇文章将介绍案例三&#xff1a;友思特Neuro-T深度学习平台进行面饼质量检测。在…

SQLynx 3.7 发布:数据库管理工具的性能与交互双重进化

目录 &#x1f511; 核心功能更新 1. 单页百万级数据展示 2. 更安全的数据更新与删除机制 3. 更智能的 SQL 代码提示 4. 新增物化视图与外表支持 5. 数据库搜索与过滤功能重构 ⚡ 总结与思考 在大数据与云原生应用快速发展的今天&#xff0c;数据库管理工具不仅要“能用…

10G网速不是梦!5G-A如何“榨干”毫米波,跑出比5G快10倍的速度?

5G-A&#xff08;5G-Advanced&#xff09;网络技术已经在中国福建省厦门市软件园成功实现万兆&#xff08;10Gbps&#xff09;速率验证&#xff0c;标志着我国正式进入5G增强版商用阶段。这一突破性成果不仅验证了5G-A技术的可行性&#xff0c;也为6G网络的发展奠定了坚实基础。…

Linux笔记---UDP套接字实战:简易聊天室

1. 项目需求分析 我们要设计的是一个简单的匿名聊天室&#xff0c;用户的客户端要求用户输入自己的昵称之后即可在一个公共的群聊当中聊天。 为了简单起见&#xff0c;我们设计用户在终端当中与客户端交互&#xff0c;而在一个文件当中显式群聊信息&#xff1a; 当用户输入的…

RTP打包与解包全解析:从RFC规范到跨平台轻量级RTSP服务和低延迟RTSP播放器实现

引言 在实时音视频系统中&#xff0c;RTSP&#xff08;Real-Time Streaming Protocol&#xff09;负责会话与控制&#xff0c;而 RTP&#xff08;Real-time Transport Protocol&#xff09;负责媒体数据承载。开发者在实现跨平台、低延迟的 RTSP 播放器或轻量级 RTSP 服务时&a…

Ubuntu 用户和用户组

一、 Linux 用户linux 是一个多用户操作系统&#xff0c;不同的用户拥有不同的权限&#xff0c;可以查看和操作不同的文件。 Ubuntu 有三种用户1、初次创建的用户2、root 用户---上帝3、普通用户初次创建的用户权限比普通用户要多&#xff0c;但是没有 root 用户多。Linux 用户…

FastGPT社区版大语言模型知识库、Agent开源项目推荐

​ FastGPT 项目说明 项目概述 FastGPT 是一个基于大语言模型&#xff08;LLM&#xff09;的知识库问答系统&#xff0c;提供开箱即用的数据处理和模型调用能力&#xff0c;支持通过可视化工作流编排实现复杂问答场景。 技术架构 前端: Next.js TypeScript Chakra UI 后…

jsencrypt公钥分段加密,支持后端解密

前端使用jsencryp实现分段加密。 解决长文本RSA加密报错问题。 支持文本包含中文。 支持后端解密。前端加密代码&#xff1a; // import { JSEncrypt } from jsencrypt const JSEncrypt require(jsencrypt) /*** 使用 JSEncrypt 实现分段 RSA 加密&#xff08;正确处理中文字符…

生成一份关于电脑电池使用情况、健康状况和寿命估算的详细 HTML 报告

核心作用 powercfg /batteryreport 是一个在 Windows 命令提示符或 PowerShell 中运行的命令。它的核心作用是&#xff1a;生成一份关于电脑电池使用情况、健康状况和寿命估算的详细 HTML 报告。 这份报告非常有用&#xff0c;特别是对于笔记本电脑用户&#xff0c;它可以帮你&…

从 0 到 1 实现 PyTorch 食物图像分类:核心知识点与完整实

食物图像分类是计算机视觉的经典任务之一&#xff0c;其核心是让机器 “看懂” 图像中的食物类别。随着深度学习的发展&#xff0c;卷积神经网络&#xff08;CNN&#xff09;凭借强大的特征提取能力&#xff0c;成为图像分类的主流方案。本文将基于 PyTorch 框架&#xff0c;从…

Python 值传递 (Pass by Value) 和引用传递 (Pass by Reference)

Python 值传递 {Pass by Value} 和引用传递 {Pass by Reference}1. Mutable Objects and Immutable Objects in Python (Python 可变对象和不可变对象)2. Pass by Value and Pass by Reference2.1. What is Pass by Value in Python?2.2. What is Pass by Reference in Python…

aippt自动生成工具有哪些?一文看懂,总有一款适合你!

在当今快节奏的工作与学习环境中&#xff0c;传统耗时的PPT制作方式已难以满足高效表达的需求。随着人工智能技术的发展&#xff0c;AI自动生成PPT工具应运而生&#xff0c;成为提升演示文稿制作效率的利器。这类工具通过自然语言处理和深度学习技术&#xff0c;能够根据用户输…

Langflow 框架中 Prompt 技术底层实现分析

Langflow 框架中 Prompt 技术底层实现分析 1. Prompt 技术概述 Langflow 是一个基于 LangChain 的可视化 AI 工作流构建框架&#xff0c;其 Prompt 技术是整个系统的核心组件之一。Prompt 技术主要负责&#xff1a; 模板化处理&#xff1a;支持动态变量替换的提示词模板变量验证…

前端、node跨域问题

前端页面访问node后端接口跨域报错 Access to XMLHttpRequest at http://192.18.31.75/api/get?namess&age19 from origin http://127.0.0.1:5500 has been blocked by CORS policy: No Access-Control-Allow-Origin header is present on the requested resource. 这个报…

超越马力欧:如何为经典2D平台游戏注入全新灵魂

在游戏开发的世界里&#xff0c;2D平台游戏仿佛是一位熟悉的老朋友。从《超级马力欧兄弟》开启的黄金时代到现在&#xff0c;这个类型已经经历了数十年的演变与打磨。当每个基础设计似乎都已被探索殆尽时&#xff0c;我们如何才能打造出一款令人耳目一新的平台游戏&#xff1f;…

基于Springboot + vue3实现的时尚美妆电商网站

项目描述本系统包含管理员和用户两个角色。管理员角色&#xff1a;商品分类管理&#xff1a;新增、查看、修改、删除商品分类。商品信息管理&#xff1a;新增、查看、修改、删除、查看评论商品信息。用户管理&#xff1a;新增、查看、修改、删除用户。管理员管理&#xff1a;查…