Linux下關於互斥鎖及同步的移植(一)
【原文:http://jazka.blog.51cto.com/809003/234732】
講完了前面關於多執行緒的基礎知識後,說一下我最近關於移植的一些體會。
將win32程式關於多執行緒的內容移植到Linux下面,不能簡單的按照函式對應來移植。不過通過下面的對應關係,再加上你對這些模式的深入瞭解,相信會移植的很成功。
Windows 訊號量是一些計數器變數,允許有限個執行緒/程式訪問共享資源。Linux POSIX 訊號量也是一些計數器變數,可以用來在 Linux 上實現 Windows 上的訊號量功能。
- 訊號量的型別: Windows 提供了有名(named)訊號量和無名(unnamed)訊號量。有名訊號量可以在程式之間進行同步。在 Linux 上,在相同程式的不同執行緒之間,則只使用 POSIX 訊號量。在程式之間,可以使用 System V 訊號量。
- 等待函式中的超時: 當在一個等待函式中使用時,可以為 Windows 訊號量物件指定超時值。在 Linux 中,並沒有提供這種功能,只能通過應用程式邏輯處理超時的問題。
在 Windows 中,可以使用
CreateSemaphore()
建立或開啟一個有名或無名的訊號量。
HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName ); |
在這段程式碼中:
lpSemaphoreAttributes
是一個指向安全性屬性的指標。如果這個指標為空,那麼這個訊號量就不能被繼承。lInitialCount
是該訊號量的初始值。lMaximumCount
是該訊號量的最大值,該值必須大於 0。lpName
是訊號量的名稱。如果該值為 NULL,那麼這個訊號量就只能在相同程式的不同執行緒之間共享。否則,就可以在不同的程式之間進行共享。
這個函式建立訊號量,並返回這個訊號量的控制程式碼。它還將初始值設定為呼叫中指定的值。這樣就可以允許有限個執行緒來訪問某個共享資源。
在 Linux 中,可以使用
sem_init()
來建立一個無名的 POSIX 訊號量,這個呼叫可以在相同程式的執行緒之間使用。它還會對訊號量計數器進行初始化:int
sem_init(sem_t *sem, int pshared, unsigned int value)
。在這段程式碼中:value
(訊號量計數器)是這個訊號量的初始值。pshared
可以忽略,因為在目前的實現中,POSIX 訊號量還不能在程式之間進行共享。
這裡要注意的是,最大值基於 demaphore.h 標頭檔案中定義的 SEM_VALUE_MAX。
在 Linux 中,
semget()
用於建立 System V 訊號量,它可以在不同整合的執行緒之間使用。可以用它來實現與 Windows 中有名訊號量相同的功能。這個函式返回一個訊號量集識別符號,它與一個引數的鍵值關聯在一起。當建立一個新訊號量集時,對於與 semid_ds
資料結構關聯在一起的訊號量,semget()
要負責將它們進行初始化,方法如下:sem_perm.cuid
和sem_perm.uid
被設定為呼叫程式的有效使用者 ID。sem_perm.cgid
和sem_perm.gid
被設定為呼叫程式的有效組 ID。sem_perm.mode
的低 9 位被設定為semflg
的低 9 位。sem_nsems
被設定為nsems
的值。sem_otime
被設定為 0。sem_ctime
被設定為當前時間。
用來建立 System V 訊號量使用的程式碼是:
int semget(key_t key, int nsems, int semflg)
。下面是對這段程式碼的一些解釋:key
是一個惟一的識別符號,不同的程式使用它來標識這個訊號量集。我們可以使用ftok()
生成一個惟一的鍵值。IPC_PRIVATE
是一個特殊的key_t
值;當使用IPC_PRIVATE
作為key
時,這個系統呼叫就會只使用semflg
的低 9 位,但卻忽略其他內容,從而新建立一個訊號量集(在成功時)。nsems
是這個訊號量集中訊號量的數量。semflg
是這個新訊號量集的許可權。要新建立一個訊號量集,您可以將使用IPC_CREAT
來設定位操作或訪問許可權。如果具有該 key 值的訊號量集已經存在,那麼IPC_CREAT
/IPC_EXCL
標記就會失敗。
注意,在 System V 訊號量中,
key
被用來惟一標識訊號量;在 Windows 中,訊號量是使用一個名稱來標識的。
為了對訊號量集資料結構進行初始化,可以使用
IPC_SET
命令來呼叫 semctl()
系統呼叫。將
arg.buf 所指向的 semid_ds 資料結構的某些成員的值寫入訊號量集資料結構中,同時更新這個結構的 sem_ctime member 的值。使用者提供的這個 arg.buf 所指向的 semid_ds 結構如下所示:sem_perm.uid
sem_perm.gid
sem_perm.mode
(只有最低 9 位有效)
呼叫程式的有效使用者 ID 應該是超級使用者,或者至少應該與這個訊號量集的建立者或所有者匹配:
int semctl(int semid, int semnum, int cmd = IPC_SET, ...)
。在這段程式碼中:semid
是訊號量集的識別符號。semnum
是訊號量子集偏移量(從 0 到nsems
-1,其中 n 是這個訊號量集中子集的個數)。這個命令會被忽略。cmd
是命令;它使用IPC_SET
來設定訊號量的值。args
是這個訊號量集資料結構中要通過IPC_SET
來更新的值(在這個例子中會有解釋)。
最大計數器的值是根據在標頭檔案中定義的
SEMVMX
來決定的。
在 Windows 中,我們使用
OpenSemaphore()
來開啟某個指定訊號量。只有在兩個程式之間共享訊號量時,才需要使用訊號量。在成功開啟訊號量之後,這個函式就會返回這個訊號量的控制程式碼,這樣就可以在後續的呼叫中使用它了。
HANDLE OpenSemaphore( DWORD dwDesiredAccess, BOOL bInheritHandle, LPCTSTR lpName ) |
在這段程式碼中:
dwDesiredAccess
是針對該訊號量物件所請求的訪問權。bInheritHandle
是用來控制這個訊號量控制程式碼是否可繼承的標記。如果該值為 TRUE,那麼這個控制程式碼可以被繼承。lpName
是這個訊號量的名稱。
在 Linux 中,可以呼叫相同的
semget()
來開啟某個訊號量,不過此時 semflg
的值為
0:int semget(key,nsems,0)
。在這段程式碼中:key
應該指向想要開啟的訊號量集的 key 值。- 為了開啟一個已經存在的訊號量,可以將
nsems
和標記設定為 0。semflg
值是在返回訊號量集識別符號之前對訪問許可權進行驗證時設定的。
在 Windows 中,等待函式提供了獲取同步物件的機制。可以使用的等待函式有多種型別;在這一節中,我們只考慮
WaitForSingleObject()
(其他型別將會分別進行討論)。這個函式使用一個訊號量物件的控制程式碼作為引數,並會一直等待下去,直到其狀態變為有訊號狀態或超時為止。DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );
在這段程式碼中:
hHandle
是指向互斥控制程式碼的指標。dwMilliseconds
是超時時間,以毫秒為單位。如果該值是INFINITE
,那麼它阻塞呼叫執行緒/程式的時間就是不確定的。
在 Linux 中,
sem_wait()
用來獲取對訊號量的訪問。這個函式會掛起呼叫執行緒,直到這個訊號量有一個非空計數為止。然後,它可以原子地減少這個訊號量計數器的值:int
sem_wait(sem_t * sem)
。
在 POSIX 訊號量中並沒有超時操作。這可以通過在一個迴圈中執行一個非阻塞的
sem_trywait()
實現,該函式會對超時值進行計算:int
sem_trywait(sem_t * sem)
。
在使用 System V 訊號量時,如果通過使用
IPC_SET
命令的 semctl()
呼叫設定初始的值,那麼必須要使用 semop()
來獲取訊號量。semop()
執行操作集中指定的操作,並阻塞呼叫執行緒/程式,直到訊號量值為
0 或更大為止:int semop(int semid, struct sembuf *sops, unsigned nsops)
。
函式
semop()
原子地執行在 sops
中所包含的操作
—— 也就是說,只有在這些操作可以同時成功執行時,這些操作才會被同時執行。sops
所指向的陣列中的每個 nsops
元素都使用 struct
sembuf
指定了一個要對訊號量執行的操作,這個結構包括以下成員:unsigned short sem_num;
(訊號量個數)short sem_op;
(訊號量操作)short sem_flg;
(操作標記)
要獲取訊號量,可以通過將
sem_op
設定為 -1 來呼叫 semop()
;在使用完訊號量之後,可以通過將 sem_op
設定為
1 來呼叫 semop()
釋放訊號量。通過將 sem_op
設定為
-1 來呼叫semop()
,訊號量計數器將會減小 1,如果該值小於 0(訊號量的值是不能小於 0 的),那麼這個訊號量就不能再減小,而是會讓呼叫執行緒/程式阻塞,直到其狀態變為有訊號狀態為止。sem_flg
中可以識別的標記是 IPC_NOWAIT
和 SEM_UNDO
。如果某一個操作被設定了SEM_UNDO
標記,那麼在程式結束時,該操作將被取消。如果 sem_op
被設定為
0,那麼semop()
就會等待 semval
變成 0。這是一個“等待為
0” 的操作,可以用它來獲取訊號量。
記住,超時操作在 System V 訊號量中並不適用。這可以在一個迴圈中使用非阻塞的
semop()
(通過將 sem_flg
設定為 IPC_NOWAIT
)實現,這會計算超時的值。
在 Windows 中,
ReleaseSemaphore()
用來釋放訊號量。
BOOL ReleaseSemaphore( HANDLE hSemaphore, LONG lReleaseCount, LPLONG lpPreviousCount ); |
在這段程式碼中:
hSemaphore
是一個指向訊號量控制程式碼的指標。lReleaseCount
是訊號量計數器,可以通過指定的數量來增加計數。lpPreviousCount
是指向上一個訊號量計數器返回時的變數的指標。如果並沒有請求上一個訊號量計數器的值,那麼這個引數可以是 NULL。
這個函式會將訊號量計數器的值增加在
lReleaseCount
中指定的值上,然後將這個訊號量的狀態設定為有訊號狀態。
在 Linux 中,我們使用
sem_post()
來釋放訊號量。這會喚醒對這個訊號量進行阻塞的所有執行緒。訊號量的計數器同時被增加 1。要為這個訊號量的計數器新增指定的值(就像是 Windows
上一樣),可以使用一個互斥變數多次呼叫以下函式:int sem_post(sem_t * sem)
。
對於 System V 訊號量來說,只能使用
semop()
來釋放訊號量:int
semop(int semid, struct sembuf *sops, unsigned nsops)
。
函式
semop()
原子地執行 sops
中包含的一組操作(只在所有操作都可以同時成功執行時,才會將所有的操作同時一次執行完)。sops
所指向的陣列中的每個 nsops
元素都使用一個 struct
sembuf
結構指定了一個要對這個訊號量執行的操作,該結構包含以下元素:unsigned short sem_num;
(訊號量個數)short sem_op;
(訊號量操作)short sem_flg;
(操作標記)
要釋放訊號量,可以通過將
sem_op
設定為 1 來呼叫 semop()
。通過將 semop()
設定為
1 來呼叫 semop()
,這個訊號量的計數器會增加 1,同時用訊號通知這個訊號量。
在 Windows 中,我們使用
CloseHandle()
來關閉或銷燬訊號量物件。
BOOL CloseHandle( HANDLE hObject ); |
hObject
是指向這個同步物件控制程式碼的指標。
在 Linux 中,
sem_destroy()
負責銷燬訊號量物件,並釋放它所持有的資源: int
sem_destroy(sem_t *sem)
。對於 System V 訊號量來說,只能使用 semctl()
函式的IPC_RMID
命令來關閉訊號量集:int
semctl(int semid, int semnum, int cmd = IPC_RMID, ...)
。
這個命令將立即刪除訊號量集及其資料結構,並喚醒所有正在等待的程式(如果發生錯誤,則返回,並將
errno
設定為 EIDRM
)。呼叫程式的有效使用者
ID 必須是超級使用者,或者可以與該訊號量集的建立者或所有者匹配的使用者。引數 semnum
會被忽略。相關文章
- Linux下關於互斥鎖及同步的移植(一)薦Linux
- Linux下關於互斥鎖及同步的移植(二)Linux
- 執行緒同步與互斥:互斥鎖執行緒
- 關於同步的一點思考-下
- 透過互斥鎖+條件量的方式實現同步與互斥
- Linux多執行緒的使用一:互斥鎖Linux執行緒
- Java虛擬機器13:互斥同步、鎖優化及synchronized和volatileJava虛擬機優化synchronized
- 關於Linux下ntp 時間同步錯誤Linux
- Linux之執行緒互斥鎖Linux執行緒
- Onvif開發之Linux下gsoap的使用及移植Linux
- Python提高:關於GIL(全域性直譯器鎖)與執行緒互斥鎖的理解Python執行緒
- 關於WINDOWS平臺下RMAN備份移植Windows
- 一文看懂臨界區、互斥鎖、同步鎖、臨界區、訊號量、自旋鎖等名詞!
- 關於CQRS及非同步一致性的疑惑非同步
- 【go】golang中鎖的用法-互斥鎖Golang
- 多執行緒(2)-執行緒同步互斥鎖Mutex執行緒Mutex
- Jtti:linux下訊號量和互斥鎖有哪些區別?JttiLinux
- linux多執行緒-----同步物件(互斥量、讀寫鎖、條件變數)的屬性Linux執行緒物件變數
- linux多執行緒-----同步機制(互斥量、讀寫鎖、條件變數)Linux執行緒變數
- Gil全域性解釋鎖和執行緒互斥鎖的關係執行緒
- Python中的互斥鎖Python
- 執行緒的互斥鎖執行緒
- liteos互斥鎖(七)
- Go併發程式設計之傳統同步—(1)互斥鎖Go程式設計
- 關於JAVA的可移植性(轉)Java
- 關於在 Linux 下多個不相干的程式互斥訪問同一片共享記憶體的問題Linux記憶體
- linux程式多執行緒互斥鎖的簡單使用Linux執行緒
- C 語言的 互斥鎖、自旋鎖、原子操作
- Go語言的互斥鎖MutexGoMutex
- 阮一峰關於js同步非同步的解釋JS非同步
- 關於Gdb工具的交叉編譯、移植編譯
- 【linux】系統程式設計-6-POSIX標準下的訊號量與互斥鎖Linux程式設計
- 物聯網學習教程——執行緒同步與互斥:讀寫鎖執行緒
- TM表鎖各種mode的實驗及2-6 的TM鎖相互間的互斥示例
- 互斥鎖mutex的簡單實現Mutex
- 特定的閂鎖和互斥場景
- MySql關於鎖的一些總結MySql
- Banq,請教一下關於非同步事件監聽模式非同步事件模式