linux的信號量初識

Linux下的信號量(Semaphore)深度解析

在多線程或多進程并發編程的領域中,確保對共享資源的安全訪問和協調不同執行單元的同步至關重要。信號量(Semaphore)作為經典的同步原語之一,在 Linux 系統中扮演著核心角色。本文將深入探討 Linux 環境下 POSIX 信號量的概念、工作原理、API 使用、示例代碼、流程圖及注意事項。

1. 什么是信號量?

信號量是由荷蘭計算機科學家艾茲格·迪科斯徹(Edsger Dijkstra)在 1965 年左右提出的一個同步機制。本質上,信號量是一個非負整數計數器,它被用于控制對一組共享資源的訪問。它主要支持兩種原子操作:

  1. P 操作 (Proberen - 測試/嘗試): 也稱為 wait(), down(), acquire()。此操作會檢查信號量的值。
    • 如果信號量的值大于 0,則將其減 1,進程/線程繼續執行。
    • 如果信號量的值等于 0,則進程/線程會被阻塞(放入等待隊列),直到信號量的值變為大于 0。
  2. V 操作 (Verhogen - 增加): 也稱為 signal(), up(), post(), release()。此操作會將信號量的值加 1。
    • 如果此時有其他進程/線程因等待該信號量而被阻塞,則系統會喚醒其中一個(或多個,取決于實現)等待的進程/線程。

核心思想: 信號量的值代表了當前可用資源的數量。當一個進程/線程需要使用資源時,它執行 P 操作;當它釋放資源時,執行 V 操作。

類比:

  • 計數信號量 (Counting Semaphore): 想象一個有 N 個停車位的停車場。信號量的初始值是 N。每當一輛車進入,信號量減 1 (P 操作)。當車位滿 (信號量為 0) 時,新來的車必須等待。每當一輛車離開,信號量加 1 (V 操作),并可能通知等待的車輛有空位了。
  • 二值信號量 (Binary Semaphore): 停車場只有一個車位 (N=1)。信號量的值只能是 0 或 1。這常被用作互斥鎖 (Mutex),確保同一時間只有一個進程/線程能訪問某個臨界區。

2. Linux 中的信號量類型

Linux 主要支持兩種信號量實現:

  1. System V 信號量: 這是較老的一套 IPC (Inter-Process Communication) 機制的一部分(還包括 System V 消息隊列和共享內存)。它功能強大但 API 相對復雜,信號量通常是內核持久的,需要顯式刪除。相關函數有 semget(), semop(), semctl()
  2. POSIX 信號量: 這是 POSIX 標準定義的一套接口,通常更推薦在新代碼中使用。它提供了更簡潔、更易于使用的 API。POSIX 信號量可以是命名信號量(可在不相關的進程間共享,通過名字訪問,如 /mysemaphore)或未命名信號量(通常在同一進程的線程間或父子進程間共享,存在于內存中)。

本文將重點關注更常用且推薦的 POSIX 未命名信號量。

3. POSIX 信號量核心 API (C/C++)

使用 POSIX 信號量需要包含頭文件 <semaphore.h>

3.1 sem_init() - 初始化未命名信號量

#include <semaphore.h>int sem_init(sem_t *sem, int pshared, unsigned int value);

功能: 初始化位于 sem 指向地址的未命名信號量。

參數:

  • sem_t *sem: 指向要初始化的信號量對象的指針。sem_t 是信號量類型。
  • int pshared: 控制信號量的共享范圍。
    • 0: 信號量在當前進程的線程間共享。信號量對象 sem 應位于所有線程都能訪問的內存區域(如全局變量、堆內存)。
    • 0: 信號量在進程間共享。信號量對象 sem 必須位于共享內存區域(例如使用 mmap 創建的共享內存段)。
  • unsigned int value: 信號量的初始值。對于二值信號量(用作鎖),通常初始化為 1;對于計數信號量,根據可用資源數量初始化。

返回值:

  • 成功: 返回 0。
  • 失敗: 返回 -1,并設置 errno。常見的 errno 包括 EINVAL (value 超過 SEM_VALUE_MAX),ENOSYS (不支持進程間共享)。

3.2 sem_destroy() - 銷毀未命名信號量

#include <semaphore.h>int sem_destroy(sem_t *sem);

功能: 銷毀由 sem_init() 初始化的未命名信號量 sem。銷毀一個正在被其他線程等待的信號量會導致未定義行為。只有在確認沒有線程再使用該信號量后才能銷毀。

參數:

  • sem_t *sem: 指向要銷毀的信號量對象的指針。

返回值:

  • 成功: 返回 0。
  • 失敗: 返回 -1,并設置 errno (如 EINVAL 表示 sem 不是一個有效的信號量)。

3.3 sem_wait() - 等待(P 操作/減 1)

#include <semaphore.h>int sem_wait(sem_t *sem);

功能: 對信號量 sem 執行 P 操作(嘗試減 1)。

  • 如果信號量的值大于 0,則原子地將其減 1,函數立即返回。
  • 如果信號量的值等于 0,則調用線程/進程將被阻塞,直到信號量的值大于 0(通常是另一個線程/進程調用 sem_post() 之后)或收到一個信號。

參數:

  • sem_t *sem: 指向要操作的信號量對象的指針。

返回值:

  • 成功: 返回 0。
  • 失敗: 返回 -1,并設置 errno
    • EINVAL: sem 不是一個有效的信號量。
    • EINTR: 操作被信號中斷。應用程序通常需要檢查 errno 并重新嘗試 sem_wait()

3.4 sem_trywait() - 非阻塞等待

#include <semaphore.h>int sem_trywait(sem_t *sem);

功能: sem_wait() 的非阻塞版本。

  • 如果信號量的值大于 0,則原子地將其減 1,函數立即返回 0。
  • 如果信號量的值等于 0,則函數立即返回 -1,并將 errno 設置為 EAGAIN,調用線程不會被阻塞。

參數:

  • sem_t *sem: 指向要操作的信號量對象的指針。

返回值:

  • 成功 (信號量減 1): 返回 0。
  • 失敗: 返回 -1,并設置 errno
    • EAGAIN: 信號量當前為 0,無法立即減 1。
    • EINVAL: sem 不是一個有效的信號量。

3.5 sem_timedwait() - 帶超時的等待

#include <semaphore.h>
#include <time.h>int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout);

功能: 類似 sem_wait(),但帶有超時限制。

  • 如果信號量的值大于 0,則原子地將其減 1,函數立即返回 0。
  • 如果信號量的值等于 0,則線程阻塞等待,但如果在 abs_timeout 指定的絕對時間(基于 CLOCK_REALTIME)到達之前信號量仍未增加,則函數返回錯誤。

參數:

  • sem_t *sem: 指向要操作的信號量對象的指針。
  • const struct timespec *abs_timeout: 指向一個 timespec 結構體,指定了阻塞等待的絕對超時時間點。struct timespec { time_t tv_sec; long tv_nsec; };

返回值:

  • 成功 (信號量減 1): 返回 0。
  • 失敗: 返回 -1,并設置 errno
    • ETIMEDOUT: 在超時時間到達前未能成功將信號量減 1。
    • EINVAL: sem 無效或 abs_timeout 無效。
    • EINTR: 操作被信號中斷。

3.6 sem_post() - 釋放(V 操作/加 1)

#include <semaphore.h>int sem_post(sem_t *sem);

功能: 對信號量 sem 執行 V 操作(原子地將其值加 1)。如果有任何線程/進程因此信號量而被阻塞,則其中一個會被喚醒。

參數:

  • sem_t *sem: 指向要操作的信號量對象的指針。

返回值:

  • 成功: 返回 0。
  • 失敗: 返回 -1,并設置 errno
    • EINVAL: sem 不是一個有效的信號量。
    • EOVERFLOW: 信號量的值增加將超過 SEM_VALUE_MAX

3.7 sem_getvalue() - 獲取信號量當前值

#include <semaphore.h>int sem_getvalue(sem_t *sem, int *sval);

功能: 獲取信號量 sem 的當前值,并將其存儲在 sval 指向的整數中。注意:獲取到的值可能在函數返回后立即就過時了(因為其他線程可能同時修改了信號量),主要用于調試或特定場景。

參數:

  • sem_t *sem: 指向要查詢的信號量對象的指針。
  • int *sval: 指向用于存儲信號量當前值的整數的指針。

返回值:

  • 成功: 返回 0。
  • 失敗: 返回 -1,并設置 errno (如 EINVAL)。

4. 工作流程圖 (sem_wait 和 sem_post)

graph TDsubgraph Thread A (Calls sem_wait)A1(Start sem_wait(sem)) --> A2{Check sem value > 0?};A2 -- Yes --> A3[Decrement sem value];A3 --> A4[Proceed];A2 -- No --> A5[Block Thread A];endsubgraph Thread B (Calls sem_post)B1(Start sem_post(sem)) --> B2[Increment sem value];B2 --> B3{Any threads blocked on sem?};B3 -- Yes --> B4[Wake up one blocked thread (e.g., Thread A)];B3 -- No --> B5[Return];B4 --> B5;endA5 --> B4;  // Woken up by Thread B's postB4 -..-> A2; // Woken Thread A re-evaluates condition

流程圖解釋:

sem_wait 流程 (Thread A):

  1. 線程 A 調用 sem_wait
  2. 檢查信號量的值是否大于 0。
    • 是: 信號量減 1,線程 A 繼續執行。
    • 否: 線程 A 被阻塞,進入等待狀態。

sem_post 流程 (Thread B):

  1. 線程 B 調用 sem_post
  2. 信號量的值加 1。
  3. 檢查是否有其他線程(如線程 A)正因該信號量而被阻塞。
    • 是: 喚醒其中一個被阻塞的線程。被喚醒的線程會回到 sem_wait 的檢查點,此時信號量值已大于 0,它將成功減 1 并繼續執行。
    • 否: 直接返回。

5. C/C++ 測試用例:使用信號量保護臨界區

這個例子演示了如何使用二值信號量(初始化為 1)來實現類似互斥鎖的功能,保護一個共享計數器,防止多個線程同時修改導致競態條件。

#include <iostream>
#include <vector>
#include <thread>
#include <semaphore.h> // For POSIX semaphores
#include <unistd.h>    // For usleep// Global shared resource
int shared_counter = 0;// Global semaphore (acting as a mutex)
sem_t mutex_semaphore;// Number of threads and increments per thread
const int NUM_THREADS = 5;
const int INCREMENTS_PER_THREAD = 100000;// Thread function
void worker_thread(int id) {for (int i = 0; i < INCREMENTS_PER_THREAD; ++i) {// --- Enter Critical Section ---if (sem_wait(&mutex_semaphore) == -1) { // P operation (wait)perror("sem_wait failed");return; // Exit thread on error}// --- Critical Section Start ---// Access shared resourceint temp = shared_counter;// Simulate some work inside the critical section// usleep(1); // Optional small delay to increase chance of race condition without semaphoreshared_counter = temp + 1;// --- Critical Section End ---if (sem_post(&mutex_semaphore) == -1) { // V operation (post)perror("sem_post failed");// Handle error if necessary, though less critical than wait failure}// --- Exit Critical Section ---}std::cout << "Thread " << id << " finished." << std::endl;
}int main() {// Initialize the semaphore// pshared = 0: shared between threads of the same process// value = 1: initial value, acting as a binary semaphore (mutex)if (sem_init(&mutex_semaphore, 0, 1) == -1) {perror("sem_init failed");return 1;}std::cout << "Starting " << NUM_THREADS << " threads, each incrementing counter "<< INCREMENTS_PER_THREAD << " times." << std::endl;std::vector<std::thread> threads;for (int i = 0; i < NUM_THREADS; ++i) {threads.emplace_back(worker_thread, i);}// Wait for all threads to completefor (auto& t : threads) {t.join();}// Destroy the semaphoreif (sem_destroy(&mutex_semaphore) == -1) {perror("sem_destroy failed");// Continue cleanup if possible}std::cout << "All threads finished." << std::endl;std::cout << "Expected final counter value: " << NUM_THREADS * INCREMENTS_PER_THREAD << std::endl;std::cout << "Actual final counter value:   " << shared_counter << std::endl;// Check if the result is correctif (shared_counter == NUM_THREADS * INCREMENTS_PER_THREAD) {std::cout << "Result is correct!" << std::endl;} else {std::cout << "Error: Race condition likely occurred!" << std::endl;}return 0;
}

編譯與運行:

# Compile using g++ (or gcc if it were pure C)
# Link with pthread library for std::thread and potentially needed by semaphore implementation
g++ semaphore_example.cpp -o semaphore_example -pthread# Run the executable
./semaphore_example

預期輸出:
程序會創建多個線程,每個線程對共享計數器執行大量遞增操作。由于信號量的保護,最終的 shared_counter 值應該等于 NUM_THREADS * INCREMENTS_PER_THREAD。如果沒有信號量保護(注釋掉 sem_waitsem_post),最終結果幾乎肯定會小于預期值,因為會發生競態條件。

6. 信號量的主要應用場景

  1. 互斥訪問 (Mutual Exclusion): 使用初始值為 1 的二值信號量來保護臨界區,確保同一時間只有一個線程/進程能訪問共享資源或執行某段代碼,功能類似互斥鎖(Mutex)。
  2. 資源計數: 使用初始值為 N 的計數信號量來管理 N 個相同的資源(如數據庫連接池中的連接、線程池中的工作線程等)。需要資源的線程執行 P 操作,釋放資源的線程執行 V 操作。
  3. 同步 (Synchronization): 協調不同線程/進程的執行順序。例如,一個線程(生產者)產生數據后執行 V 操作,另一個線程(消費者)在執行 P 操作時等待,直到有數據可用。

7. 注意事項與最佳實踐

  1. 成對使用 sem_waitsem_post: 在保護臨界區的場景下,每個 sem_wait 都必須有對應的 sem_post。忘記 sem_post 會導致資源永久鎖定(死鎖的一種形式),而錯誤地多調用 sem_post 會破壞互斥性。
  2. 初始化與銷毀: 確保在使用前正確調用 sem_init 初始化信號量,并在不再需要時調用 sem_destroy 銷毀它。對于進程間共享的信號量,銷毀邏輯需要特別注意。
  3. 錯誤檢查: 務必檢查 sem_init, sem_wait, sem_trywait, sem_timedwait, sem_post, sem_destroy 等函數的返回值,并在失敗時根據 errno 進行適當的錯誤處理。
  4. 處理 EINTR: sem_waitsem_timedwait 可能會被信號中斷(返回 -1 且 errnoEINTR)。健壯的程序應該捕獲這種情況并通常重新嘗試等待操作。
  5. 死鎖 (Deadlock): 當多個線程/進程相互等待對方持有的信號量時,會發生死鎖。設計鎖的獲取順序是避免死鎖的關鍵策略之一。例如,總是按相同的固定順序獲取多個信號量。
  6. 避免在信號處理函數中使用 sem_wait: 信號處理函數的執行環境受限。在信號處理函數中調用可能阻塞的函數(如 sem_wait)通常是不安全的,可能導致

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/pingmian/79113.shtml
繁體地址,請注明出處:http://hk.pswp.cn/pingmian/79113.shtml
英文地址,請注明出處:http://en.pswp.cn/pingmian/79113.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

《Android 應用開發基礎教程》——第十一章:Android 中的圖片加載與緩存(Glide 使用詳解)

目錄 第十一章&#xff1a;Android 中的圖片加載與緩存&#xff08;Glide 使用詳解&#xff09; &#x1f539; 11.1 Glide 簡介 &#x1f538; 11.2 添加 Glide 依賴 &#x1f538; 11.3 基本用法 ? 加載網絡圖片到 ImageView&#xff1a; ? 加載本地資源 / 文件 / UR…

AE模板 300個故障干擾損壞字幕條標題動畫視頻轉場預設

這個AE模板提供了300個故障干擾損壞字幕條標題動畫視頻轉場預設&#xff0c;讓您的視頻具有炫酷的故障效果。無論是預告片、宣傳片還是其他類型的視頻&#xff0c;這個模板都能帶給您令人驚嘆的故障運動標題效果。該模板無需任何外置插件或腳本&#xff0c;只需一鍵點擊即可應用…

在 Python 中,以雙下劃線開頭和結尾的函數(如 `__str__`、`__sub__` 等)

在 Python 中&#xff0c;以雙下劃線開頭和結尾的函數&#xff08;如 __str__、__sub__ 等&#xff09;被稱為特殊方法&#xff08;Special Methods&#xff09;或魔術方法&#xff08;Magic Methods&#xff09;。它們確實是 Python 內置的&#xff0c;用于定義類的行為&#…

git問題記錄-如何切換歷史提交分支,且保留本地修改

問題記錄 我在本地編寫了代碼&#xff0c;突然想查看之前提交的代碼&#xff0c;并且想保留當前所在分支所做的修改 通過git stash對本地的代碼進行暫存 使用git checkout <commit-hash>切換到之前的提交記錄。 查看完之后我想切換回來&#xff0c;恢復暫存的本地代碼…

Github開通第三方平臺OAuth登錄及Java對接步驟

調研起因&#xff1a; 準備搞AI Agent海外項目&#xff0c;有相當一部分用戶群體是程序員&#xff0c;所以當然要接入Github這個全球最大的同性交友網站了&#xff0c;讓用戶使用Github賬號一鍵完成注冊或登錄。 本教程基于Web H5界面進行對接&#xff0c;同時也提供了spring-…

期刊、出版社、索引數據庫

image 1、研究人員向期刊或者會議投稿&#xff0c;交注冊費和相應的審稿費等相關費用[1]&#xff1b; 2、會議組織者和期刊聯系出版社&#xff0c;交出版費用&#xff1b; 3、出版社將論文更新到自己的數據庫中&#xff0c;然后將數據庫賣給全世界各大高校或企業&#xff1b; 4…

Transformer 模型及深度學習技術應用

近年來&#xff0c;隨著卷積神經網絡&#xff08;CNN&#xff09;等深度學習技術的飛速發展&#xff0c;人工智能迎來了第三次發展浪潮&#xff0c;AI技術在各行各業中的應用日益廣泛。 注意力機制&#xff1a;理解其在現代深度學習中的關鍵作用&#xff1b; Transformer模型…

zynq7035的arm一秒鐘最多可以支持觸發多少次中斷

一、概述 1.關于zynq7035的ARM處理器一秒能夠支持多少次中斷觸發&#xff0c;需要綜合來考慮。需要確定ARM處理器的參數&#xff0c;目前zynq7000系列&#xff0c;使用的雙核Cortex-A9處理器。其中主頻大概在500MHZ~1GHZ左右&#xff0c;不同的用戶配置的主頻可能稍微有差別。 …

數據結構與算法:圖論——最短路徑

最短路徑 先給出一些leetcode算法題&#xff0c;以后遇見了相關題目再往上增加 最短路徑的4個常用算法是Floyd、Bellman-Ford、SPFA、Dijkstra。不同應用場景下&#xff0c;應有選擇地使用它們&#xff1a; 圖的規模小&#xff0c;用Floyd。若邊的權值有負數&#xff0c;需要…

[android]MT6835 Android 關閉selinux方法

Selinux SELinux is an optional feature of the Linux kernel that provides support to enforce access control security policies to enforce MAC. It is based on the LSM framework. Working with SELinux on Android – LineageOS Android 關閉selinux MT6835 Android…

【Linux網絡編程】http協議的狀態碼,常見請求方法以及cookie-session

本文專欄&#xff1a;Linux網絡編程 目錄 一&#xff0c;狀態碼 重定向狀態碼 1&#xff0c;永久重定向&#xff08;301 Moved Permanently&#xff09; 2&#xff0c;臨時重定向&#xff08;302 Found&#xff09; 二&#xff0c;常見請求方法 1&#xff0c;HTTP常見Hea…

當神經網絡突破摩爾定律:探索大模型時代的算力新紀元

當摩爾定律熄滅后&#xff1a;AI算力革命如何重塑技術文明的底層邏輯 一、摩爾定律的黃昏&#xff1a;物理極限與經濟理性的雙重困境 當英特爾在1965年提出摩爾定律時&#xff0c;沒有人預料到這個每18-24個月將芯片晶體管數量翻倍的預言會成為現代計算文明的基石。半個世紀以…

位運算題目:尋找重復數

文章目錄 題目標題和出處難度題目描述要求示例數據范圍進階 前言解法一思路和算法代碼復雜度分析 解法二思路和算法代碼復雜度分析 解法三思路和算法代碼復雜度分析 題目 標題和出處 標題&#xff1a;尋找重復數 出處&#xff1a;287. 尋找重復數 難度 6 級 題目描述 要…

Elasticsearch:沒有 “AG” 的 RAG?

作者&#xff1a;來自 Elastic Gustavo Llermaly 了解如何利用語義搜索和 ELSER 構建一個強大且視覺上吸引人的問答體驗&#xff0c;而無需使用 LLMs。 想要獲得 Elastic 認證&#xff1f;查看下一期 Elasticsearch Engineer 培訓的時間&#xff01; Elasticsearch 擁有眾多新…

linux下安裝ollama網不好怎么辦?

文章目錄 前言kkgithub下載腳本,而不是直接運行修改腳本修改權限還是不行?前言 今天想在linux上面更新一下ollama,于是去到官網: https://ollama.com/download/linux linux下安裝ollama還是挺簡單的: curl -fsSL https://ollama.com/install.sh | sh我也是特別嗨皮地就…

相機-IMU聯合標定:相機-IMU外參標定

文章目錄 ??簡介??標定工具kalibr??標定數據錄制??相機-IMU外參標定??簡介 在 VINS(視覺慣性導航系統) 中,相機-IMU外參標定 是確保多傳感器數據時空統一的核心環節,其作用可概括為以下關鍵點: 坐標系對齊(空間同步),外參誤差會導致視覺特征點投影與IMU預積…

基于 Java 的實現前端組裝查詢語句,后端直接執行查詢方案,涵蓋前端和后端的設計思路

1. 前端設計 前端負責根據用戶輸入或交互條件,動態生成查詢參數,并通過 HTTP 請求發送到后端。 前端邏輯: 提供用戶界面(如表單、篩選器等),讓用戶選擇查詢條件。將用戶選擇的條件組裝成 JSON 格式的查詢參數。發送 HTTP 請求(如 POST 或 GET)到后端。示例: 假設用…

[STM32] 4-2 USART與串口通信(2)

文章目錄 前言4-2 USART與串口通信(2)數據發送過程雙緩沖與連續發送數據發送過程中的問題 數據接收過程TXE標志位&#xff08;發送數據寄存器空&#xff09;TC標志位&#xff08;發送完成標志位&#xff09;單個數據的發送數據的連續發送 接收過程中遇到的問題問題描述&#xf…

Qt多線程TCP服務器實現指南

在Qt中實現多線程TCP服務器可以通過為每個客戶端連接分配獨立的線程來處理&#xff0c;以提高并發性能。以下是一個分步實現的示例&#xff1a; 1. 自定義工作線程類&#xff08;處理客戶端通信&#xff09; // workerthread.h #include <QObject> #include <QTcpSo…

詳細介紹Python-pandas-DataFrame全部 *功能* 函數

Python-pandas-DataFrame全部 功能 函數 提示&#xff1a;幫幫志會陸續更新非常多的IT技術知識&#xff0c;希望分享的內容對您有用。本章分享的是pandas的使用語法。前后每一小節的內容是存在的有&#xff1a;學習and理解的關聯性。【幫幫志系列文章】&#xff1a;每個知識點…