日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

【Linux】POSIX信号量

發布時間:2023/12/31 linux 46 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【Linux】POSIX信号量 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 一. 什么是POSIX信號量?
  • 二. 為什么要有POSIX信號量?
  • 三. POSIX信號量實現原理
  • 四. POSIX信號量接口函數
    • 1. 創建、初始化信號量
    • 2. 銷毀信號量
    • 3. 等待(申請)信號量
    • 4. 發布(釋放)信號量
  • 五. 信號量的應用
    • 1. 二元信號量模擬互斥鎖
    • 2. 基于環形隊列的生產者消費者模型
      • 2.1 基本規則
      • 2.2 環形隊列的實現
      • 2.3 單生產者單消費者
      • 2.4 多生產者多消費者
  • 六. 信號量和條件變量的區別

一. 什么是POSIX信號量?

POSIX和System V都是可移植的操作系統接口標準,它們都定義了操作系統應該為應用程序提供的接口標準。

  • POSIX信號量和System V信號量作用相同,都是用于同步和互斥操作,以達到無沖突的訪問共享資源目的。
  • System V版本的信號量只適用于實現進程間的通信,而POSIX版本的信號量主要用于實現線程之間的通信。

信號量(信號燈)本質是一個是用來對臨界資源進行更細粒度地描述和管理的計數器。

二. 為什么要有POSIX信號量?

POSIX信號量主要用于實現線程間的同步。

三. POSIX信號量實現原理

信號量的結構如下:

  • count:記錄還有多少小塊的臨界資源未被使用。
  • queue:當count為0時,其它未申請到信號量的線程的task_struct地址會被放到信號量等待隊列中阻塞掛起。

信號量的PV操作

  • P操作:我們把申請信號量得操作稱為P操作,申請信號量的本質就是申請獲得整塊臨界資源中某小塊資源的使用權限,當申請成功時臨界資源中小塊資源的數目應該減一,因此P操作的本質就是讓count- -。
  • V操作:我們將釋放信號量稱為V操作,釋放信號量的本質就是歸還臨界資源中某塊資源的使用權限,當釋放成功時臨界資源中資源的數目就應該加一,因此V操作的本質就是讓count++。

申請不到信號量的線程被阻塞掛起

當count為0時,表示不允許其它線程再訪問臨界資源,這時其它申請信號量的線程會被阻塞到該信號量的等待隊列中,直到其它線程釋放信號量。

四. POSIX信號量接口函數

1. 創建、初始化信號量

信號量的類型是sem_t,我們可以根據這個類型自己定義信號量對象:

定義出信號量對象之后,必須用sem_init()函數來初始化這個信號量:

參數說明:

  • sem:信號量對象的地址。
  • pshared:0表示線程間共享,非零表示進程間共享。
  • value:信號量初始值,即count的大小。

函數說明:該函數主要用于設置信號量對象的基本屬性。

2. 銷毀信號量


函數說明:只需傳入信號量對象的地址即可銷毀該信號量。

3. 等待(申請)信號量


函數說明:傳入信號量對象的地址用于申請該信號量,調用成功返回0,count- -;失敗返回-1,count值不變。

4. 發布(釋放)信號量


函數說明:傳入信號量對象的地址用于釋放該信號量,調用成功返回0,count++;失敗返回-1,count值不變。

五. 信號量的應用

1. 二元信號量模擬互斥鎖

當count = 1時,說明整塊臨界資源作為一個整體使用而沒有被切分管理,那么這個信號量對象就相當于是一把互斥鎖,稱為二元信號量。

下面我們用二元信號量模擬互斥鎖完成黃牛搶票代碼:

  • 在主線程中創建4個新線程去搶10張票。
  • 此時票是臨界資源,我們用二元信號量對其進行保護。
  • 每個新線程搶票之前都要先申請二元信號量,沒有申請到線程被阻塞掛起。
#include <iostream> #include <unistd.h> #include <pthread.h> #include <semaphore.h> using namespace std; // 封裝一個自己的信號量類 class MySem { public: // 構造函數,用于初始化信號量對象,默認線程間通信,只需傳入需要設置的count得值即可 MySem(size_t num) { sem_init(&_sem, 0, num); } // 析構函數,銷毀信號量對象 ~MySem() { sem_destroy(&_sem); } // 申請信號量void P(){sem_wait(&_sem);}// 釋放信號量void V(){sem_post(&_sem);}private:// 成員變量是一個信號量對象sem_t _sem; };// 定義的全局對象 int count = 10;// 票數設為10張 MySem sem(1);// 一元信號量// 新線程執行的搶票邏輯 void* GetTickets(void* arg) {while(true){size_t id = (size_t)arg;sem.P();if(count > 0){usleep(1000);cout<<'['<<"thread "<<id<<']'<<" get ticket No."<<count--<<endl;sem.V();}else { sem.V();break;}}return nullptr; }int main() {// 創建4個新線程pthread_t tids[4];for(size_t i = 0; i < 4; ++i){pthread_create(&tids[i], nullptr, GetTickets, (void*)(i+1));}// 等待4個新線程for(size_t i = 0; i < 4; ++i){pthread_join(tids[i], nullptr);}return 0; }

編譯運行,由于我們沒有實現同步所以都是第一個創建的1號線程申請到信號量,但是最終票的結果是對的,說明互斥是實現了的:

2. 基于環形隊列的生產者消費者模型

2.1 基本規則

  • 生產者只關心是否還有格子用來生產數據。
  • 消費者只關心環形隊列中是否還有數據。
  • 一開始沒有數據,生產者和消費者指向同一個位置,這時生產者要先執行生產操作,消費者阻塞掛起;數據滿時,生產者和消費者也指向同一個位置,這時消費者先執行消費操作再輪到生產者生產。
  • 生產者和消費者不能同時訪問隊列中的同一個位置。
  • 生產者和消費者可以并發訪問環形隊列中的不同位置。
  • 2.2 環形隊列的實現

    成員變量說明:

    • 這里用一個數組來模擬環形隊列,因為生產者和消費者要并發執行且不能同時操作相同位置的數據,剛好數組可以通過下標隨機訪問數據,所以這里我們選用數組。
    • 定義了兩個無符號整型對象_proPos和_cusPos分別指向生產者要生產數據的格子下標和消費者要拿取數據的位置下標。
    • 還定義了_proSem和_cusSem兩個信號量對象,分別記錄著環形隊列中格子數量和以生產數據個數。
    • 最后還有必要記錄環形隊列的容量大小,可以用它來取模更新_proPos和_cusPos的值。
    #pragma once #include <vector> #include <time.h> #include <iostream> #include <unistd.h> #include <semaphore.h> using namespace std;// 環形隊列容量缺省值 const size_t NUM = 8;// 環形隊列主體 template<class T> class RingQueue {public:RingQueue(size_t num = NUM):_v(num),_cusPos(0),_proPos(0),_capacity(num){sem_init(&_cusSem, 0, 0);sem_init(&_proSem, 0, num);}~RingQueue(){sem_destroy(&_cusSem);sem_destroy(&_proSem);}// 生產者生產數據void Push(const T& inData){P(_proSem);_v[_proPos] = inData;V(_cusSem);++_proPos;_proPos %= _capacity;}// 消費者消費數據void Pop(T& outData){P(_cusSem);outData = _v[_cusPos];V(_proSem); ++_cusPos; _cusPos %= _capacity; } private: // 申請信號量 void P(sem_t& s) { sem_wait(&s); } // 釋放信號量 void V(sem_t& s) { sem_post(&s); } sem_t _cusSem; // 記錄隊列中空格數量的信號量 sem_t _proSem; // 記錄隊列中數據數量的信號量 size_t _cusPos; // 記錄當前空格所在下標 size_t _proPos; // 記錄當前數據所在下標 vector<T> _v; // 用數組模擬環形隊列 size_t _capacity;// 記錄環形隊列容量 };

    成員函數說明:

    • 這里特意封裝了信號量的PV操作,只需把信號量對象作為參數傳入就能完成信號量的申請、釋放操作。
    • 生產者執行Push()操作生產數據時,需要先申請(減一)_proSem信號量,生產完成后釋放(加一)_cusPos信號量,讓消費者來消費。反之亦然

    2.3 單生產者單消費者

    在主線程中創建兩個新線程分別代表生產者和消費者,消費者每隔一秒地從環形隊列中拿取數據,生產者每隔一秒生產一個數據:

    // 基于環形隊列的單生產者單消費者模型 #include "RingQueue.h"// 消費者線程執行的操作 void* Customer(void* arg) {RingQueue<int>* q = (RingQueue<int>*)arg;while(true){sleep(1);int getData;q->Pop(getData);cout<<"[Customer] pop data:"<<getData<<endl;} }// 生產者線程執行的操作 void* Producer(void* arg) {RingQueue<int>* q = (RingQueue<int>*)arg;while(true){sleep(1);int putData = (rand()%100) + 1;q->Push(putData);cout<<"[Producer] push data:"<<putData<<endl;} }int main() { // 1、制造隨機數種子,作為生產者push到環形隊列當中的數據 srand((size_t)time(nullptr)); // 2、new一個環形隊列 RingQueue<int>* q = new RingQueue<int>; // 3、分別創建、等待一個生產者和一個消費者 pthread_t tid1, tid2; pthread_create(&tid1, nullptr, Customer, (void*)q); pthread_create(&tid2, nullptr, Producer, (void*)q); pthread_join(tid1, nullptr); pthread_join(tid2, nullptr); // 4、最后delete環形隊列 delete q; return 0; }

    編譯運行,由于_proSem初始值為0,一開始沒有數據生產者線程要掛起等待,消費者生產一個數據,生產者就拿取一個數據:

    接下來我們讓生產者生產得快,消費者消費的慢:

    編譯運行,發現生產者生產的數據瞬間把隊列填滿了,接下來消費者拿走一個數據,生產者再生產一個數據,二者串行執行:

    如果消費者消費得快,生產者生產得慢的話,可以推測結果是生產者生產完一個數據,消費者馬上就拿走,然后繼續等待生產者生產數據,這個就不在做演示了。

    2.4 多生產者多消費者

    這次我們在主線程中分別新建三個生產者線程、三個消費者線程。生產者之間競爭proLock這把鎖,消費者之間競爭cusLock這把鎖,競爭到鎖的線程才能去生產或拿取數據,它們完成一次操作后釋放鎖,然后重新內部競爭:

    // 基于環形隊列的多生產者多消費者模型 #include "RingQueue.h"// 構造兩個全局互斥鎖對象,分別用于所有生產者和所有消費者線程 pthread_mutex_t cusLock; pthread_mutex_t proLock;// new一個存儲整數的全局環形隊列 RingQueue<int>* q = new RingQueue<int>;// 消費者線程執行的操作 void* Customer(void* arg) {while(true){size_t id = (size_t)arg;int getData;pthread_mutex_lock(&cusLock);q->Pop(getData); pthread_mutex_unlock(&cusLock); cout<<'['<<"Customer "<<id<<']'<<" Pop data:"<<getData<<endl;sleep(1);} }// 生產者線程執行的操作 void* Producer(void* arg) {size_t id = (size_t)arg;while(true){int putData = (rand()%100) + 1;pthread_mutex_lock(&proLock);q->Push(putData);pthread_mutex_unlock(&proLock);cout<<'['<<"Producer "<<id<<']'<<" push data "<<putData<<endl;sleep(1);} }int main() {// 1、初始化兩把全局互斥鎖pthread_mutex_init(&cusLock, nullptr);pthread_mutex_init(&proLock, nullptr);// 2、創造種子,用于生產隨機數據插入到環形隊列中srand((size_t)time(nullptr));// 3、分別新建三個生產者、消費者線程pthread_t cusTids[3];pthread_t proTids[3];for(size_t i = 0; i < 3; ++i){pthread_create(&cusTids[i], nullptr, Customer, (void*)(i+1));}for(size_t i = 0; i < 3; ++i){pthread_create(&proTids[i], nullptr, Producer, (void*)(i+1)); }// 4、分別等待三個生產者、消費者線程for(size_t i = 0; i < 3; ++i){pthread_join(cusTids[i], nullptr);}for(size_t i = 0; i < 3; ++i){pthread_join(proTids[i], nullptr);}// 5、等待完成后delete環形隊列并銷毀互斥鎖對象delete q;pthread_mutex_destroy(&cusLock);pthread_mutex_destroy(&proLock);return 0; }

    編譯運行,生產和消費操作并發執行:

    六. 信號量和條件變量的區別

    信號量既可以實現同步還可以實現互斥,而條件變量只能實現同步;條件變量需要搭配互斥鎖使用,而信號量通過自身計數器實現同步的條件判斷,不需要搭配互斥鎖使用。

    總結

    以上是生活随笔為你收集整理的【Linux】POSIX信号量的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。