寫在前面
1. 本文內容對應《UNIX環境高級編程》(第2版)》第11章。
2. 總結了線程同步的三種方法:互斥量、讀寫鎖以及條件變量。
3. 希望本文對您有所幫助,也歡迎您給我提意見和建議。
線程同步是一個老話題了。當多個控制線程共享相同的內存時,需要確保每個線程看到一致的數據視圖。APUE介紹的線程同步方式有:
--------------------------------------------------------------------------------
互斥量
互斥量(mutex)從本質上說是一把鎖,在訪問共享資源前對互斥量進行加鎖,在訪問完成后釋放互斥量上的鎖。互斥變量用pthread_mutex_t數據類型來表示,在使用前必須對其進行初始化。對于靜態分配的互斥量,可以把它設置為常量PTHREAD_MUTEX_INITIALIZER。如果動態地分配互斥量,可以通過調用pthread_mutex_init函數進行初始化,并且在釋放內存前需要調用pthread_mutex_destroy。當參數attr置為NULL時,使用默認的屬性初始化互斥量。
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
All return: 0 if OK, error number on failure
對互斥量加鎖需要調用pthread_mutex_lock,如果互斥量已經上鎖,調用線程將阻塞直到互斥量被解鎖。對互斥量解鎖,需要調用pthread_mutex_unlock。如果線程不希望被阻塞,它可以使用pthread_mutex_trylock嘗試對互斥量進行加鎖。如果調用pthread_mutex_trylock時互斥量處于未鎖住狀態,那么pthread_mutex_trylock將鎖住互斥量,不會出現阻塞并返回0,否則pthread_mutex_trylock就會失敗,不能鎖住互斥量,而返回EBUSY。
如果線程試圖對同一個互斥量加鎖兩次,那么它自身就會陷入死鎖狀態。可以通過小心地控制互斥量加鎖的順序來避免死鎖的發生。只有一個線程試圖以與另一個線程相反的順序鎖住互斥量時,才可能出現死鎖。如果無法控制互斥量加鎖的順序,可以在試圖加鎖時,先釋放占有的鎖,然后過段時間再試。
--------------------------------------------------------------------------------
讀寫鎖
讀寫鎖與互斥量類似,不過讀寫鎖允許更高的并行性。讀寫鎖可以有三種狀態:讀模式下加鎖狀態,寫模式下加鎖狀態,以及不加鎖狀態。一次只有一個線程可以占有寫模式的讀寫鎖,但是多個線程可以同時占有讀模式的讀寫鎖。因此,讀寫鎖也叫做共享-獨占鎖。讀寫鎖非常適用于對數據結構讀的次數遠大于寫的情況。
與互斥量一樣,讀寫鎖在使用之前必須初始化,在釋放它們底層的內存前必須銷毀。這通過pthread_rwlock_inti和pthread_rwlock_destroy函數完成。如果希望讀寫鎖有默認的屬性,可以傳一個空指針給attr。要在讀模式下鎖定讀寫鎖,需要調用pthread_rwlock_rdlock;要在寫模式下鎖定讀寫鎖,需要調用pthread_rwlock_wrlock。不管以何種方式鎖住讀寫鎖,都可以調用pthread_rwlock_unlock進行解鎖。如果希望線程不被阻塞,可以調用pthread_rwlock_tryrdlock和pthread_rwlock_trywrlock函數嘗試對讀寫鎖進行加鎖。
#include <pthread.h>
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr);
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
All return: 0 if OK, error number on failure
--------------------------------------------------------------------------------
條件變量
條件變量與互斥量一起使用時,允許線程以無競爭的方式等待特定的條件發生。條件本身是由互斥量保護的,線程在改變條件前必須首先鎖住互斥量,且只有在鎖住互斥量以后才能計算條件。條件變量使用之前必須首先進行初始化,pthread_cond_t數據類型代表的條件變量可以用兩種方式初始化。可以把常量PTHREAD_COND_INITIALIZER賦給靜態分配的條件變量,但是如果條件變量是動態分配的,可以使用pthread_cond_init函數進行初始化。在釋放底層的內存空間前,可以使用pthread_mutex_destroy函數對條件變量進行銷毀。除非需要創建一個非默認屬性的條件變量,否則pthread_cond_init函數的attr參數可以設置為NULL。
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *restrict cond,
pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
All return: 0 if OK, error number on failure
使用pthread_cond_wait等待條件變為真,如果在給定時間內條件不能滿足,那么會生成一個代表出錯碼的返回值。調用者需要把鎖住的互斥量傳給pthread_cond_wait對條件進行保護。函數把調用線程放到等待條件的線程列表上,然后對互斥量解鎖,這兩個操作是原子操作。當pthread_cond_wait返回時,互斥量再次被鎖住。
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
int pthread_cond_timedwait(pthread_cond_t *restrict cond,
pthread_mutex_t *restrict mutex,
const struct timespec *restrict timeout);
All return: 0 if OK, error number on failure
pthread_cond_timedwait函數的工作方式與pthread_cond_wait函數相似。timeout值指定了等待的時間,它通過timespec結構指定。時間值用秒數或者分秒數表示,分秒數的單位是納秒。時間值是一個絕對數而不是相對數。可以使用gettimeofday獲取用timeval結構表示的當前時間,然后把這個時間加上要等待的時間轉換成timespec結構。
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
void maketimeout(struct timespec *tsp, long minutes)
{
struct timeval now;
/* get the current time */
gettimeofday(&now);
tsp->tv_sec = now.tv_sec;
tsp->tv_nsec = now.tv_usec * 1000; /* usec to nsec */
/* add the offset to get timeout value */
tsp->tv_sec += minutes * 60;
}
如果時間值到了但是條件還沒有出現,pthread_cond_timedwait將重新獲取互斥量,然后返回錯誤ETIMEDOUT。從pthread_cond_wait或者pthread_cond_timedwait調用成功返回時,線程需要重新計算條件,因為其它線程可能已經在運行并改變了條件。
pthread_cond_signal函數將喚醒等待該條件的某個線程,而pthread_cond_broadcast函數將喚醒等待該條件的所有線程。必須注意一定要在改變條件狀態以后再喚醒等待線程。
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
All return: 0 if OK, error number on failure
使用范例如下:
#include <pthread.h>
struct msg {
struct msg *m_next;
/* ... more stuff here ... */
};
struct msg *workq;
pthread_cond_t qready = PTHREAD_COND_INITIALIZER; /*初始化條件變量*/
pthread_mutex_t qlock = PTHREAD_MUTEX_INITIALIZER; /*初始化互斥量*/
void process_msg(void)
{
struct msg *mp;
for (;;) {
pthread_mutex_lock(&qlock); /*條件本身由互斥量保護*/
while (workq == NULL) /*wait返回后要重新檢查條件*/
pthread_cond_wait(&qready, &qlock); /*wait期間釋放互斥量,返回時再次鎖住*/
mp = workq;
workq = mp->m_next;
pthread_mutex_unlock(&qlock); /*真正釋放互斥量*/
/* now process the message mp */
}
}
void enqueue_msg(struct msg *mp)
{
pthread_mutex_lock(&qlock); /*修改條件前鎖住互斥量*/
mp->m_next = workq;
workq = mp;
pthread_mutex_unlock(&qlock);
pthread_cond_signal(&qready); /*喚醒等待線程時不需要占有互斥量*/
/*如果希望在wait返回時不用再檢查條件,
就需要在喚醒時占有互斥量*/
}
本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/intrepyd/archive/2009/08/20/4467821.aspx