Linux下關於互斥鎖及同步的移植(二)

查志強發表於2014-06-17

【原文:http://jazka.blog.51cto.com/809003/234734

繼續上一篇,這篇來對比事件在win32和Linux下的區別。
 
在 Windows 中,事件物件是那些需要使用 SetEvent() 函式顯式地將其狀態設定為有訊號狀態的同步物件。事件物件來源有兩種型別:
  • 手工重置事件(manual reset event) 中,物件的狀態會一直維持為有訊號狀態,直到使用 ResetEvent() 函式顯式地重新設定它為止。
  • 自動重置事件(auto reset event) 中,物件的狀態會一直維持為有訊號狀態,直到單個正在等待的執行緒被釋放為止。當正在等待的執行緒被釋放時,其狀態就被設定為無訊號的狀態。
事件物件有兩種狀態,有訊號(signaled)狀態無訊號(non-signaled)狀態。對事件物件呼叫的等待函式會阻塞呼叫執行緒,直到其狀態被設定為有訊號狀態為止。
在進行平臺的遷移時,需要考慮以下問題:
  • Windows 提供了 有名(named)無名(un-named) 的事件物件。有名事件物件用來在程式之間進行同步,而在 Linux 中, pthreads 和 POSIX 都提供了執行緒間的同步功能。為了在 Linux 實現與 Windows 中有名事件物件相同的功能,可以使用 System V 訊號量或訊號。
  • Windows 提供了兩種型別的事件物件 —— 手工重置物件和自動重置物件。Linux 只提供了自動重置事件的特性。
  • 在 Windows 中,事件物件的初始狀態被設定為有訊號狀態。在 Linux 中,pthreads 並沒有提供初始狀態,而 POSIX 訊號量則提供了一個初始狀態。
  • Windows 事件物件是非同步的。在 Linux 中,POSIX 訊號量和 System V 訊號量也都是非同步的,不過 pthreads 條件變數不是非同步的。
  • 當在一個等待函式中使用事件物件時,可以指定 Windows 的事件物件的超時時間值。在 Linux 中,只有 pthreads 在等待函式中提供了超時的特性。
還有幾點非常重要,需要說明一下:
  • 儘管 POSIX 訊號量是計數器訊號量,但是當這個計數器被設定為 1 時,它們可以提供與 Windows 事件物件相似的功能。它們並不能在等待函式中提供超時時間。如果在進行移植時,超時並不是一個影響因素,那麼建議您使用 POSIX 訊號量。
  • 當與互斥一起使用時,pthreads 條件變數可以線上程之間提供基於事件的同步機制,不過這是同步的。根據應用程式的邏輯,這可以將此作為移植過程中在 Linux 上實現這種功能的一個選擇。
在 Windows 中,我們使用 CreateEvent() 來建立事件物件。
HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL bManualReset,
  BOOL bInitialState,
  LPCTSTR lpName
)
在這段程式碼中:
  • lpEventAttributes 是一個指標,它指向一個決定這個控制程式碼是否能夠被繼承的屬性。如果這個指標為 NULL,那麼這個物件就不能被初始化。
  • bManualReset 是一個標記,如果該值為 TRUE,就會建立一個手工重置的事件,應該顯式地呼叫 ResetEvent(),將事件物件的狀態設定為無訊號狀態。
  • bInitialState 是這個事件物件的初始狀態。如果該值為 true,那麼這個事件物件的初始狀態就被設定為有訊號狀態。
  • lpName 是指向這個事件物件名的指標。對於無名的事件物件來說,該值是 NULL。
這個函式建立一個手工重置或自動重置的事件物件,同時還要設定改物件的初始狀態。這個函式返回事件物件的控制程式碼,這樣就可以在後續的呼叫中使用這個事件物件了。
OpenEvent() 用來開啟一個現有的有名事件物件。這個函式返回該事件物件的控制程式碼。
HANDLE OpenEvent(
  DWORD dwDesiredAccess,
  BOOL bInheritHandle,
  LPCTSTR lpName
)
在這段程式碼中:
  • dwDesiredAccess 是針對這個事件物件所請求的訪問權。
  • bInheritHandle 是用來控制這個事件物件控制程式碼是否可繼承的標記。如果該值為 TRUE,那麼這個控制程式碼就可以被繼承;否則就不能被繼承。
  • lpName 是一個指向事件物件名的指標。
在 Linux 中,可以呼叫 sem_init() 來建立一個 POSIX 訊號量:int sem_init(sem_t *sem, int pshared, unsigned int value)(其中 value(即訊號量計數值)被設定為這個訊號量的初始狀態)。
Linux pthreads 使用 pthread_cond_init() 來建立一個條件變數:int pthread_cond_init(pthread_cond_t *cond, pthread_condattr_t *cond_attr)
 
可以使用 PTHREAD_COND_INITIALIZER 常量靜態地對 pthread_cond_t 型別的條件變數進行初始化,也可以使用 pthread_condattr_init() 對其進行初始化,這個函式會對與這個條件變數關聯在一起的屬性進行初始化。可以呼叫 pthread_condattr_destroy() 用來銷燬屬性:
int pthread_condattr_init(pthread_condattr_t *attr)
int pthread_condattr_destroy(pthread_condattr_t *attr)

等待某個事件
在 Windows 中,等待函式提供了獲取同步物件的機制。我們可以使用不同型別的等待函式(此處我們只考慮 WaitForSingleObject())。這個函式會使用一個互斥物件的控制程式碼,並一直等待,直到它變為有訊號狀態或超時為止。
DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD dwMilliseconds
);
在這段程式碼中:
  • hHandle 是指向互斥控制程式碼的指標。
  • dwMilliseconds 是超時時間的值,單位是毫秒。如果該值為 INFINITE,那麼它阻塞呼叫執行緒/程式的時間就是不確定的。
Linux POSIX 訊號量使用 sem_wait() 來掛起呼叫執行緒,直到訊號量的計數器變成非零的值為止。然後它會自動減小訊號量計數器的值:int sem_wait(sem_t * sem)
在 POSIX 訊號量中並沒有提供超時操作。這可以通過在一個迴圈中執行非阻塞的 sem_trywait() 來實現,該函式會對超時時間進行計數:int sem_trywait(sem_t * sem).
 
Linux pthreads 使用 pthread_cond_wait() 來阻塞呼叫執行緒,其時間是不確定的:int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex)。在另外一方面,如果呼叫執行緒需要被阻塞一段確定的時間,那麼就可以使用 pthread_cond_timedwait() 來阻塞這個執行緒。如果在這段指定的時間內條件變數並沒有出現,那麼 pthread_cond_timedwait() 就會返回一個錯誤:int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex,const struct timespec *abstime)。在這裡,abstime 引數指定了一個絕對時間(具體來說,就是從 1970 年 1 月 1 日 0 時 0 分 0 秒到現在所經過的時間。)
 
函式 SetEvent() 用來將事件物件的狀態設定為有訊號狀態。對一個已經設定為有訊號狀態的事件物件再次執行該函式是無效的。
BOOL SetEvent(
  HANDLE hEvent
)
Linux POSIX 訊號量使用 sem_post() 來發出一個事件訊號量。這會喚醒在該訊號量上阻塞的所有執行緒:int sem_post(sem_t * sem)
呼叫 pthread_cond_signal() 被用在 LinuxThreads 中,以喚醒在某個條件變數上等待的一個執行緒,而 pthread_cond_broadcast() 用來喚醒在某個條件變數上等待的所有執行緒。
int pthread_cond_signal(pthread_cond_t *cond)
int pthread_cond_broadcast(pthread_cond_t *cond)
注意,條件函式並不是非同步訊號安全的,因此不能在訊號處理函式中呼叫。具體地說,在訊號處理函式中呼叫 pthread_cond_signal() pthread_cond_broadcast() 可能會導致呼叫執行緒的死鎖。
 
在 Windows 中,ResetEvent() 用來將事件物件的狀態重新設定為無訊號狀態。
BOOL ResetEvent(
  HANDLE hEvent
);
在 Linux 中,條件變數和 POSIX 訊號量都是自動重置型別的。
 
在 Windows 中,CloseHandle() 用來關閉或銷燬事件物件。
BOOL CloseHandle(
  HANDLE hObject
);
在這段程式碼中,hObject 是指向同步物件控制程式碼的指標。
在 Linux 中, sem_destroy()/ pthread_cond_destroy() 用來銷燬訊號量物件或條件變數,並釋放它們所持有的資源:
int sem_destroy(sem_t *sem)
int pthread_cond_destroy(pthread_cond_t *cond)
在 Linux 中,程式之間有名事件物件所實現的功能可以使用 System V 訊號量實現。System V 訊號量是計數器變數,因此可以實現 Windows 中事件物件的功能,訊號量的計數器的初始值可以使用 semctl() 設定為 0。
要將某個事件的狀態修改為有訊號狀態,可以使用 semop(),並將 sem_op 的值設定為 1。要等待某個事件,則可以使用 semop() 函式,並將 sem_op 的值設定為 -1,這樣就可以阻塞呼叫程式,直到它變為有訊號狀態為止。
可以通過使用 semctl() 將訊號量計數器的初始值設定為 0 來獲得訊號量。在使用完共享資源之後,可以使用 semop() 將訊號量計數設定為 1。關於每個 System V 訊號量的原型,請參閱本文中有關訊號量一節的內容。 

相關文章