【并發編程六】c++進程通信——信號量(semaphore)
- 一、概述
- 二、信號量
- 三、原理
- 四、過程
- 五、demo
- 六、輸出
- 七、windows api介紹
- 1. 創建信號量 CreateSemaphore()
- 2. 打開信號量 OpenSemaphore()
- 3. 等待 WaitForSingleObject()
- 4. 遞增信號量的當前資源計數ReleaseSemaphore()
- 5. 關閉句柄 CloseHandle()
- 八、信號量使用場景
為了防?多進程競爭共享資源,?造成的數據錯亂,所以需要保護機制,使得共享的資源,在任意時刻只能被?個進程訪問(或者有限個進程訪問)。正好,信號量就實現了這?保護機制。
一、概述
信號量的概念是由荷蘭計算機科學家艾茲赫爾·戴克斯特拉(Edsger W. Dijkstra)發明的,廣泛的應用于不同的操作系統中。在系統中,給予每一個進程一個信號量,代表每個進程目前的狀態,未得到控制權的進程會在特定地方被強迫停下來,等待可以繼續進行的信號到來。如果信號量是一個任意的整數,通常被稱為計數信號量(Counting semaphore),或一般信號量(general semaphore);如果信號量只有二進制的0或1,稱為二進制信號量(binary semaphore)
- 信號量是操作系統提供的一種協調共享資源訪問的方法。信號量則由操作系統進行管理,地位高于進程,操作系統保證信號量的原子性。
二、信號量
信號量(英語:semaphore)又稱為信號標或者信號燈,是一個同步對象,用于保持在0至指定最大值之間的一個計數值。當線程完成一次對該semaphore對象的等待(wait)時,該計數值減一;當線程完成一次對semaphore對象的釋放(release)時,計數值加一。當計數值為0,則線程等待該semaphore對象不再能成功直至該semaphore對象變成signaled狀態。semaphore對象的計數值大于0,為signaled狀態;計數值等于0,為nonsignaled狀態。
三、原理
一個信號量 S 是個整型變量,它除了初始化外只能通過兩個標準原子操作:wait () 和 signal() 來訪問:
- 操作 wait() 最初稱為 P(荷蘭語proberen,測試);成功則,S–;
- 操作 signal() 最初稱為 V(荷蘭語verhogen,增加);成功則,S++;
四、過程
我們以windows api的接口為例,講解下信號量是如何在進程A和進程B間做到進程間同步的。
1、進程A過程
- 1.1、CreateSemaphore():創建一個名字為Semaphore的信號量,該信號量初始可使用的資源數為0。即S=0.
- 1.2、WaitForSingleObject():等待信號量>0,就是等待信號量的資源數大于0時。成功后就是S–。(啟動進程A后,此處會一直等待,因為創建的信號量初始的值=0,直到進程B打開進程A的信號量,并且釋放一個可以使用的資源時,S變成1,才可以繼續,進行后面的程序)
- 1.3、在屏幕打印文字。
- 1.4、ReleaseSemaphore():釋放上面wait成功時占用的1個資源數。執行成功后就是S++。
- 1.5、等待5s。
2、進程B過程
- 2.1、OpenSemaphore():打開進程A創建的信號量,名字為Semaphore
- 2.2、ReleaseSemaphore():遞增信號量的當前資源計數,就是S++。S=1
- 2.3、WaitForSingleObject():等待信號量>0,就是等待信號量的資源數大于0時。成功后就是S–。
- 2.4、在屏幕打印文字。
- 2.5、ReleaseSemaphore():釋放上面wait成功時占用的1個資源數。成功后就是S++。
- 2.6、等待5s。
五、demo
1、進程A
#include <windows.h>
#include <iostream>
using namespace std;#define BUF_SIZE 4096int main(int argc, TCHAR* argv[])
{cout << "processA:" << endl << endl;// 創建信號量HANDLE handle = CreateSemaphore(NULL,0,10,"Semaphore");cout << "創建信號量成功,并等待進程B釋放一個可以使用的資源數......." << endl<<endl;for (int i = 0; i < 5; i++){cout << "進入新號量等待區" << endl;DWORD ret = WaitForSingleObject(handle,100000);if (0 == ret){cout << "processA:" << i << ".....s--" << endl;}ReleaseSemaphore(handle , 1,NULL);cout << "釋放1個信號并等待5秒中....." << "s++" << endl << endl;Sleep(5000);} system("pause"); //等待其他進程讀取數據// 關閉句柄CloseHandle(handle);return 0;
}
CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0)
PROJECT(process)
ADD_EXECUTABLE(processA main.cpp)
ADD_SUBDIRECTORY(processB)
SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
2、進程B
#include <iostream>
#include <windows.h>
using namespace std;#define BUF_SIZE 4096int main()
{cout << "processB:" << endl << endl;// 打開共享的文件對象HANDLE handle = OpenSemaphore(SEMAPHORE_ALL_ACCESS, // 訪問標志TRUE, // 繼承標志"Semaphore"); // 信號量名if (nullptr != handle){cout << "打開信號量:成功" << endl;}else{cout << "打開信號量:失敗" << endl;}cout << "釋放1個可以使用的資源數:s++" << endl<<endl;ReleaseSemaphore(handle, 1, NULL);for (int i = 0; i < 5; i++){cout << "進入新號量等待區" << endl;DWORD ret = WaitForSingleObject(handle,100000);if (0 == ret){cout << "processB:" << i <<".....s--"<< endl;}ReleaseSemaphore(handle, 1, NULL);cout << "釋放1個信號并等待5秒中....." << "s++" << endl << endl;Sleep(5000);}system("pause");return 0;
}
CMAKE_MINIMUM_REQUIRED(VERSION 3.8.0)SET(TARGET "childprocess")ADD_EXECUTABLE(processB main.cpp)SET(LIBRARY_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
SET(EXECUTABLE_OUTPUT_PATH "${PROJECT_SOURCE_DIR}/lib")
六、輸出
構建、編譯、先啟動進程A,輸出如下。(備:因為創建信號量時,初始化資源數為0,即S=0,如果你不啟動進程B釋放可用的資源數,進程A在此處會等待直到超時)
再啟動進程B輸出如下。
七、windows api介紹
本文我們以windows的api接口為例,其實Linux下接口原理是一樣的,用法也類似,我們不再過多介紹。
1. 創建信號量 CreateSemaphore()
函數說明:
第一個參數表示安全控制,一般直接傳入NULL。
第二個參數表示初始資源數量。
第三個參數表示最大并發數量。
第四個參數表示信號量的名稱,傳入NULL表示匿名信號量。
HANDLE WINAPI CreateSemaphore( _In_opt_ LPSECURITY_ATTRIBUTES lpEventAttributes, // 安全屬性,通常為NULL_In_ LONG lInitialCount, // 可用資源數目,就是信號量S的初始值_In_ LONG lMaximumCount, // 信號量對象可處理的最大資源數,就是S的最大值_In_opt_ LPCTSTR lpName // 信號量的名稱,進程之間可共享
);
2. 打開信號量 OpenSemaphore()
函數說明:
第一個參數表示訪問權限,對一般傳入SEMAPHORE_ALL_ACCESS。詳細解釋可以查看MSDN文檔。
第二個參數表示信號量句柄繼承性,一般傳入TRUE即可。
第三個參數表示名稱,不同進程中的各線程可以通過名稱來確保它們訪問同一個信號量。
HANDLE WINAPI OpenSemaphore( _In_ DWORD dwDesiredAccess, // 訪問的權限_In_ BOOL bInheritHandle, // 子進程繼承信號量對象與否_In_ LPCTSTR lpName // 信號量對象名稱
);
3. 等待 WaitForSingleObject()
等待指定對象處于信號狀態或超時間隔已過。
本文中,就是等待信號量S>0時,進入。如果信號量S<=0,那就是等待,直到設置的超時時間。
DWORD WINAPI WaitForSingleObject( _In_ HANDLE hHandle, // 內核對象句柄_In_ DWORD dwMilliseconds // 單位,毫秒。對象被觸發前的等待時間,INFINITE表示無限時間阻塞等待,簡單來說就是死等
);
4. 遞增信號量的當前資源計數ReleaseSemaphore()
函數說明:
第一個參數是信號量的句柄。
第二個參數表示增加個數,必須大于0且不超過最大資源數量。
第三個參數可以用來傳出先前的資源計數,設為NULL表示不需要傳出。
BOOL WINAPI ReleaseSemaphore( _In_ HANDLE hSemaphore, // 信號量對象句柄_In_ LONG lReleaseCount, // 釋放使用的資源樹_Out_opt_ LPLONG lpPreviousCount // 一般設為null
);
當一個線程使用完信號量對象控制的有限資源后,應該調用ReleaseSemaphore,釋放使用的資源,使信號量對象的當前資源計數得到恢復
5. 關閉句柄 CloseHandle()
BOOL WINAPI CloseHandle( _In_ HANDLE hObject // 句柄
);
八、信號量使用場景
- 多進程訪問統一資源。
- 資源限流。
比如:數據庫連接池,同時進行連接的線程有數量限制,連接不能超過一定的數量,當連接達到了限制數量后,后面的線程只能排隊等前面的線程釋放了數據庫連接才能獲得數據庫連接。
關于資源限流,我們舉例說下。
1、一個廁所有3個坑,進廁所需要拿1個號牌(5毛錢一個號牌)。
2、肯定要設置為信號量的初始值為S=3,就是廁所的號牌數量為3。
3、進去一個人,號牌減1,S–。
4、當進入3個人時,沒有號牌了,S=0,第四個人就要等待了,
5、等待廁所中的其中一個人上完大號出來,把號牌交出去,S++,其他人才可以拿著號牌進去。
(其實,你想想網站服務器控制并發訪問和限流,不也是這個原理嗎?)
- 參考
1、多線程面試題系列(8):經典線程同步 信號量Semaphore
2、 信號量與管程
3、 信號量及其使用和實現(超詳細)
4、 維基百科-信號量
4、 信號量限流,高并發場景不得不說的秘密