講完了前面關於多線程的基礎知識後,說一下我最近關於移植的一些體會。 將win32程序關於多線程的內容移植到Linux下面,不能簡單的按照函數對應來移植。不過通過下面的對應關系,再加上你對這些模式的深入了解,相信會移植的很成功。 信號量 Windows 信號量是一些計數器變量,允許有限個線程/進程訪問共享資源。Linux POSIX 信號量也是一些計數器變量,可以用來在 Linux 上實現 Windows 上的信號量功能。
CreateSemaphore()
創建或打開一個有名或無名的信號量。
HANDLE CreateSemaphore( LPSECURITY_ATTRIBUTES lpSemaphoreAttributes, LONG lInitialCount, LONG lMaximumCount, LPCTSTR lpName );在這段代碼中:
lpSemaphoreAttributes
是一個指向安全性屬性的指針。如果這個指針為空,那麼這個信號量就不能被繼承。
lInitialCount
是該信號量的初始值。
lMaximumCount
是該信號量的最大值,該值必須大於 0。
lpName
是信號量的名稱。如果該值為 NULL,那麼這個信號量就只能在相同進程的不同線程之間共享。否則,就可以在不同的進程之間進行共享。 sem_init()
來創建一個無名的 POSIX 信號量,這個調用可以在相同進程的線程之間使用。它還會對信號量計數器進行初始化:int sem_init(sem_t *sem, int pshared, unsigned int value)
。在這段代碼中:
value
信號量計數器)是這個信號量的初始值。
pshared
可以忽略,因為在目前的實現中,POSIX 信號量還不能在進程之間進行共享。 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
被設置為當前時間。 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
標記就會失敗。 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 位有效) 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
是這個信號量的名稱。 semget()
來打開某個信號量,不過此時 semflg
的值為 0:int semget(key,nsems,0)
。在這段代碼中:
key
應該指向想要打開的信號量集的 key 值。
nsems
和標記設置為 0。semflg
值是在返回信號量集標識符之前對訪問權限進行驗證時設置的。 WaitForSingleObject()
其他類型將會分別進行討論)。這個函數使用一個信號量對象的句柄作為參數,並會一直等待下去,直到其狀態變為有信號狀態或超時為止。
DWORD WaitForSingleObject( HANDLE hHandle, DWORD dwMilliseconds );
在這段代碼中:
hHandle
是指向互斥句柄的指針。
dwMilliseconds
是超時時間,以毫秒為單位。如果該值是 INFINITE
,那麼它阻塞調用線程/進程的時間就是不確定的。 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
會被忽略。