● 互斥鎖
互斥鎖用來保證同一時間內只有一個線程在執行某段代碼(臨界區)。多線程編程最容易出問題的地方,就是臨界區的界定和訪問
控制。下面是一個生產者,消費者的簡單例子。生產者、消費者公用一個緩沖區,這里假定緩沖區只能存放一條消息。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <sys/time.h> static char buff[50]; int have_msg=0; pthread_mutex_t mutex; int delay=1; void consumeItem(char *buff) { printf("consumer item\n"); } void produceItem(char *buff) { printf("produce item\n"); } void *consumer(void *param) { while (1) { pthread_mutex_lock(&mutex); if (have_msg>0) { have_msg--; consumeItem(buff); } pthread_mutex_unlock(&mutex); sleep(delay); } return NULL; } void *producer(void *param) { while (1) { pthread_mutex_lock(&mutex); if (have_msg==0) { have_msg++; produceItem(buff); } pthread_mutex_unlock(&mutex); sleep(delay); } return NULL; } int main() { pthread_t tid_c, tid_p; void *retval; pthread_mutex_init(&mutex, NULL); pthread_create(&tid_p, NULL, producer, NULL); pthread_create(&tid_c, NULL, consumer, NULL); pthread_join(tid_p, &retval); pthread_join(tid_c, &retval); return 0; }
輸出一定是這樣的:
互斥鎖最簡單的使用是這樣的:
pthread_mutex_t mutex; //定義鎖
pthread_mutex_init(&mutex, NULL); //默認屬性初始化鎖
pthread_mutex_lock(&mutex); //申請鎖
...
pthread_mutex_unlock(&mutex); //釋放鎖
設置鎖的屬性
函數pthread_mutexattr_setpshared和函數pthread_mutexattr_settype用來設置互斥鎖屬性。
前一個函數設置屬性pshared,它有兩個取值,PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用來不同進程中的線程同步,后者用於同步本進程的不同線程。在上面的例子中,我們使用的是默認屬性PTHREAD_PROCESS_ PRIVATE。后者用來設置互斥鎖類型,可選的類型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它們分別定義了不同的上所、解鎖機制,一般情況下,選用最后一個默認屬性。用法如下:
pthread_mutexattr_t mutexAttr; pthread_mutexattr_init(&mutexAttr); pthread_mutexattr_setpshared(&mutexAttr, PTHREAD_PROCESS_PRIVATE); pthread_mutexattr_settype(&mutexAttr, PTHREAD_MUTEX_DEFAULT); pthread_mutex_init(&mutex, &mutexAttr);
pthread_mutex_lock阻塞線程直到pthread_mutex_unlock被調用。
還有另外兩個函數可以用:int pthread_mutex_trylock (pthread_mutex_t *__mutex), 該函數立即返回,根據返回狀態判斷加鎖是否成功。int pthread_mutex_timedlock (pthread_mutex_t *mutex, struct timespec *__restrict),該函數超時返回。
互斥鎖主要用來互斥訪問臨界區。用於線程的互斥。
● 條件變量
條件變量用來阻塞線程等待某個事件的發生,並且當等待的事件發生時,阻塞線程會被通知。
互斥鎖一個明顯的缺點是它只有兩種狀態:鎖定和非鎖定。而條件變量通過允許線程阻塞和等待另一個線程發送信號的方法彌補了互斥鎖的不足,它常和互斥鎖一起使用。使用時,條件變量被用來阻塞一個線程,當條件不滿足時,線程往往解開相應的互斥鎖並等待條件發生變化。一旦其它的某個線程改變了條件變量,它將通知相應的條件變量喚醒一個或多個正被此條件變量阻塞的線程。這些線程將重新鎖定互斥鎖並重新測試條件是否滿足。一般說來,條件變量被用來進行線承間的同步。
條件變量的結構為pthread_cond_t
最簡單的使用例子:
pthread_cond_t cond; pthread_cond_init(&cond, NULL); pthread_cond_wait(&cond, &mutex); ... pthread_cond_signal(&cond);
調用pthread_cond_wait的線程會按調用順序進入等待隊列,當有一個信號產生時,先進入阻塞隊列的線程先得到喚醒。條件變量在某些方面看起來差不多。
正如上面所說的,條件變量彌補了互斥鎖的不足。
接着上面列舉的生產者、消費者例子中,我們這里增加消費者(並假設緩沖區可以放任意多條信息),比如有3個消費者線程,如果都使用互斥鎖,那么三個線程都要不斷的去查看緩沖區是否有消息,有就取走。這無疑是資源的極大浪費。如果我們用條件變量,三個消費者線程都可以放心的“睡覺”,緩沖區有消息,消費者會收到通知,那時起來收消息就好了。
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <sys/time.h> static char buff[50]; pthread_mutex_t mutex; pthread_mutex_t cond_mutex; pthread_cond_t cond; void consumeItem(char *buff) { printf("consumer item\n"); } void produceItem(char *buff) { printf("produce item\n"); } void *consumer(void *param) { int t = *(int *)param; while (1) { pthread_cond_wait(&cond, &cond_mutex); pthread_mutex_lock(&mutex); printf("%d: ", t); consumeItem(buff); pthread_mutex_unlock(&mutex); } return NULL; } void *producer(void *param) { while (1) { pthread_mutex_lock(&mutex); produceItem(buff); pthread_mutex_unlock(&mutex); pthread_cond_signal(&cond); sleep(1); } return NULL; } int main() { pthread_t tid_c, tid_p, tid_c2, tid_c3; void *retval; pthread_mutex_init(&mutex, NULL); pthread_mutex_init(&cond_mutex, NULL); pthread_cond_t cond; pthread_cond_init(&cond, NULL); int p[3] = {1, 2, 3}; //用來區分是哪個線程 pthread_create(&tid_p, NULL, producer, NULL); pthread_create(&tid_c, NULL, consumer, &p[0]); pthread_create(&tid_c2, NULL, consumer, &p[1]); pthread_create(&tid_c3, NULL, consumer, &p[2]); pthread_join(tid_p, &retval); pthread_join(tid_c, &retval); pthread_join(tid_c2, &retval); pthread_join(tid_c3, &retval); return 0; }
要注意的是,條件變量只是起阻塞和喚醒線程的作用,具體的判斷條件還需用戶給出,例如緩沖區到底有多少條信息等。
與條件變量有關的其它函數有:
/* 銷毀條件變量cond */
int pthread_cond_destroy (pthread_cond_t *cond);
/* 喚醒所有等待條件變量cond的線程 */
int pthread_cond_broadcast (pthread_cond_t *cond);
/* 等待條件變量直到超時 */
int pthread_cond_timedwait (pthread_cond_t *cond,
pthread_mutex_t *mutex,
const struct timespec *abstime);
/* 初始化條件變量屬性 attr*/
int pthread_condattr_init (pthread_condattr_t *attr);
/* 銷毀條件變量屬性 */
int pthread_condattr_destroy (pthread_condattr_t *attr)
/* 讀取條件變量屬性attr的進程共享標志 */
int pthread_condattr_getpshared (const pthread_condattr_t *
attr,
int *pshared);
/* 更新條件變量屬性attr的進程共享標志 */
int pthread_condattr_setpshared (pthread_condattr_t *attr,
int pshared);
一般來說,條件變量被用來進行線程間的同步。
● 信號量
信號量本質上是一個非負的整數計數器,它被用來控制對公共資源的訪問。當公共資源增加時,調用函數sem_post()增加信號量。
只有當信號量值大於0時,才能使用公共資源,使用后,函數sem_wait()減少信號量。函數sem_trywait()和函數
pthread_ mutex_trylock()起同樣的作用,它是函數sem_wait()的非阻塞版本。
下面我們逐個介紹和信號量有關的一些函數,它們都在頭文件/usr/include/semaphore.h中定義。
信號量的數據類型為結構sem_t,它本質上是一個長整型的數。函數sem_init()用來初始化一個信號量。它的原型為:
extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));
sem為指向信號量結構的一個指針;pshared不為0時此信號量在進程間共享,否則只能為當前進程的所有線程共享;value給出了信號量的初始值。
函數sem_post( sem_t *sem )用來增加信號量的值。當有線程阻塞在這個信號量上時,調用這個函數會使其中的一個線程不在阻塞,選擇機制同樣是由線程的調度策略決定的。
函數sem_wait( sem_t *sem )被用來阻塞當前線程直到信號量sem的值大於0,解除阻塞后將sem的值減一,表明公共資源經使用后減少。函數sem_trywait ( sem_t *sem )是函數sem_wait()的非阻塞版本,它直接將信號量sem的值減一。
函數sem_destroy(sem_t *sem)用來釋放信號量sem。
在上面的生產者、消費者例子中,我們假設緩沖區最多能放10條消息。用信號量來實現如下:
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <semaphore.h> static char buff[50]; pthread_mutex_t mutex; pthread_mutex_t cond_mutex; pthread_cond_t cond; sem_t msg_cnt; //緩沖區消息數 sem_t space_cnt; //緩沖區空閑數 void consumeItem(char *buff) { printf("consumer item\n"); } void produceItem(char *buff) { printf("produce item\n"); } void *consumer(void *param) { while (1) { sem_wait(&msg_cnt); pthread_mutex_lock(&mutex); consumeItem(buff); pthread_mutex_unlock(&mutex); sem_post(&space_cnt); } return NULL; } void *producer(void *param) { while (1) { sem_wait(&space_cnt); pthread_mutex_lock(&mutex); produceItem(buff); pthread_mutex_unlock(&mutex); sem_post(&msg_cnt); } return NULL; } int main() { pthread_t tid_c, tid_p; void *retval; pthread_mutex_init(&mutex, NULL); pthread_mutex_init(&cond_mutex, NULL); pthread_cond_t cond; pthread_cond_init(&cond, NULL); sem_init(&msg_cnt, 0, 0); //初始緩沖區沒有消息 sem_init(&space_cnt, 0, 10); //初始緩沖區能放10條消息 pthread_create(&tid_p, NULL, producer, NULL); pthread_create(&tid_c, NULL, consumer, NULL); pthread_join(tid_p, &retval); pthread_join(tid_c, &retval); return 0; }