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
英文地址,請注明出處:http://en.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;查…