一、互斥鎖(mutex)
   鎖機制是同一時刻只允許一個線程執行一個關鍵部分的代碼。
 
1 . 初始化鎖
 
int pthread_mutex_init(pthread_mutex_t *mutex,const pthread_mutex_attr_t *mutexattr); 
其中參數 mutexattr 用于指定鎖的屬性(見下),如果為NULL則使用缺省屬性。
 互斥鎖的屬性在創建鎖的時候指定,在LinuxThreads實現中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經被鎖定的互斥鎖加鎖時表現不同。當前有四個值可供選擇:
 (1)PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當一個線程加鎖以后,其余請求鎖的線程將形成一個等待隊列,并在解鎖后按優先級獲得鎖。這種鎖策略保證了資源分配的公平性。
 (2)PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個線程對同一個鎖成功獲得多次,并通過多次unlock解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新競爭。
 (3)PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個線程請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同。這樣就保證當不允許多次加鎖時不會出現最簡單情況下的死鎖。
 (4)PTHREAD_MUTEX_ADAPTIVE_NP,適應鎖,動作最簡單的鎖類型,僅等待解鎖后重新競爭。
 
2 . 阻塞加鎖
 
 int pthread_mutex_lock(pthread_mutex *mutex); 
3 . 非阻塞加鎖
 
  int pthread_mutex_trylock( pthread_mutex_t *mutex); 
該函數語義與 pthread_mutex_lock() 類似,不同的是在鎖已經被占據時返回 EBUSY 而不是掛起等待。
 4 . 解鎖(要求鎖是lock狀態,并且由加鎖線程解鎖)
 
 int pthread_mutex_unlock(pthread_mutex *mutex); 
5 . 銷毀鎖(此時鎖必需unlock狀態,否則返回EBUSY)
 
int pthread_mutex_destroy(pthread_mutex *mutex); 
 示例代碼:
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
int gn;void* thread(void *arg)
{printf("thread's ID is  %d\n",pthread_self());pthread_mutex_lock(&mutex);gn = 12;printf("Now gn = %d\n",gn);pthread_mutex_unlock(&mutex);return NULL;
}int main()
{pthread_t id;printf("main thread's ID is %d\n",pthread_self());gn = 3;printf("In main func, gn = %d\n",gn);if (!pthread_create(&id, NULL, thread, NULL)) {printf("Create thread success!\n");} else {printf("Create thread failed!\n");}pthread_join(id, NULL);pthread_mutex_destroy(&mutex);return 0;
} 
二、條件變量(cond)
 
  條件變量是利用線程間共享全局變量進行同步的一種機制。條件變量上的基本操作有:觸發條件(當條件變為 true 時);等待條件,掛起線程直到其他線程觸發條件。
 
1 . 初始化條件變量 
 
   int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr); 
 盡管POSIX標準中為條件變量定義了屬性,但在Linux中沒有實現,因此cond_attr值通常為NULL,且被忽略。
 
2 . 有兩個等待函數
 (1)無條件等待
 
      int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex); 
  (2)計時等待
 
      int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime); 
  如果在給定時刻前條件沒有滿足,則返回ETIMEOUT,結束等待,其中abstime以與time()系統調用相同意義的絕對時間形式出現,0表示格林尼治時間1970年1月1日0時0分0秒。
 
 無論哪種等待方式,都必須和一個互斥鎖配合,以防止多個線程同時請求(用 pthread_cond_wait() 或 pthread_cond_timedwait() 請求)競爭條件(Race Condition)。mutex互斥鎖必須是普通鎖(PTHREAD_MUTEX_TIMED_NP)或者適應鎖(PTHREAD_MUTEX_ADAPTIVE_NP),且在調用pthread_cond_wait()前必須由本線程加鎖(pthread_mutex_lock()),而在更新條件等待隊列以前,mutex保持鎖定狀態,并在線程掛起進入等待前解鎖。在條件滿足從而離開pthread_cond_wait()之前,mutex將被重新加鎖,以與進入pthread_cond_wait()前的加鎖動作對應。
 
3 . 激發條件
 (1)激活一個等待該條件的線程(存在多個等待線程時按入隊順序激活其中一個)  
 
      int pthread_cond_signal(pthread_cond_t *cond); 
(2)激活所有等待線程
 
  int pthread_cond_broadcast(pthread_cond_t *cond); 
4 . 銷毀條件變量
 
   int pthread_cond_destroy(pthread_cond_t *cond); 
  只有在沒有線程在該條件變量上等待的時候才能銷毀這個條件變量,否則返回EBUSY
 
說明:
 
  1. pthread_cond_wait 自動解鎖互斥量(如同執行了pthread_unlock_mutex),并等待條件變量觸發。這時線程掛起,不占用CPU時間,直到條件變量被觸發(變量為ture)。在調用 pthread_cond_wait之前,應用程序必須加鎖互斥量。pthread_cond_wait函數返回前,自動重新對互斥量加鎖(如同執行了pthread_lock_mutex)。
 
  2. 互斥量的解鎖和在條件變量上掛起都是自動進行的。因此,在條件變量被觸發前,如果所有的線程都要對互斥量加鎖,這種機制可保證在線程加鎖互斥量和進入等待條件變量期間,條件變量不被觸發。條件變量要和互斥量相聯結,以避免出現條件競爭——個線程預備等待一個條件變量,當它在真正進入等待之前,另一個線程恰好觸發了該條件(條件滿足信號有可能在測試條件和調用pthread_cond_wait函數(block)之間被發出,從而造成無限制的等待)。
 
  3. 條件變量函數不是異步信號安全的,不應當在信號處理程序中進行調用。特別要注意,如果在信號處理程序中調用 pthread_cond_signal 或 pthread_cond_boardcast 函數,可能導致調用線程死鎖
 
示例代碼1:
 
#include <stdio.h>
#include <pthread.h>
#include "stdlib.h"
#include "unistd.h"pthread_mutex_t mutex;
pthread_cond_t cond;void hander(void *arg)
{free(arg);(void)pthread_mutex_unlock(&mutex);
}void *thread1(void *arg)
{pthread_cleanup_push(hander, &mutex);while (1) {printf("thread1 is running\n");pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex);printf("thread1 applied the condition\n");pthread_mutex_unlock(&mutex);sleep(4);}pthread_cleanup_pop(0);
}void *thread2(void *arg)
{while (1) {printf("thread2 is running\n");pthread_mutex_lock(&mutex);pthread_cond_wait(&cond,&mutex);printf("thread2 applied the condition\n");pthread_mutex_unlock(&mutex);sleep(1);}
}int main()
{pthread_t thid1,thid2;printf("condition variable study!\n");pthread_mutex_init(&mutex,NULL);pthread_cond_init(&cond,NULL);pthread_create(&thid1,NULL,thread1,NULL);pthread_create(&thid2,NULL,thread2,NULL);sleep(1);do {pthread_cond_signal(&cond);} while(1);sleep(20);pthread_exit(0);return 0;
} 
#include <pthread.h>
#include <unistd.h>
#include "stdio.h"
#include "stdlib.h"static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;struct node
{int n_number;struct node *n_next;
}*head = NULL;static void cleanup_handler(void *arg)
{printf("Cleanup handler of second thread.\n");free(arg);(void)pthread_mutex_unlock(&mtx);
}static void *thread_func(void *arg)
{struct node *p = NULL;pthread_cleanup_push(cleanup_handler, p);while (1) {// 這個mutex主要是用來保證pthread_cond_wait的并發性。pthread_mutex_lock(&mtx);while (head == NULL) {/* 這個while要特別說明一下,單個pthread_cond_wait功能很完善,為何* 這里要有一個while (head == NULL)呢?因為pthread_cond_wait里的線* 程可能會被意外喚醒,如果這個時候head != NULL,則不是我們想要的情況。* 這個時候,應該讓線程繼續進入pthread_cond_wait* pthread_cond_wait會先解除之前的pthread_mutex_lock鎖定的mtx,* 然后阻塞在等待對列里休眠,直到再次被喚醒(大多數情況下是等待的條件成立* 而被喚醒,喚醒后,該進程會先鎖定先pthread_mutex_lock(&mtx);,再讀取資源* 用這個流程是比較清楚的。*/pthread_cond_wait(&cond, &mtx);p = head;head = head->n_next;printf("Got %d from front of queue\n", p->n_number);free(p);}pthread_mutex_unlock(&mtx); // 臨界區數據操作完畢,釋放互斥鎖。}pthread_cleanup_pop(0);return 0;
}int main(void)
{pthread_t tid;int i;struct node *p;/* 子線程會一直等待資源,類似生產者和消費者,但是這里的消費者可以是多個消費者,* 而不僅僅支持普通的單個消費者,這個模型雖然簡單,但是很強大。*/pthread_create(&tid, NULL, thread_func, NULL);sleep(1);for (i = 0; i < 10; i++){p = (struct node*)malloc(sizeof(struct node));p->n_number = i;pthread_mutex_lock(&mtx); // 需要操作head這個臨界資源,先加鎖。p->n_next = head;head = p;pthread_cond_signal(&cond);pthread_mutex_unlock(&mtx); //解鎖sleep(1);}printf("thread 1 wanna end the line.So cancel thread 2.\n");/* 關于pthread_cancel,有一點額外的說明,它是從外部終止子線程,子線程會在最近的取消點,* 退出線程,而在我們的代碼里,最近的取消點肯定就是pthread_cond_wait()了。*/pthread_cancel(tid);pthread_join(tid, NULL);printf("All done -- exiting\n");return 0;
} 
可以看出,等待條件變量信號的用法約定一般是這樣的:
 
...
pthread_mutex_lock(&mutex);
...
pthread_cond_wait (&cond, &mutex);
...
pthread_mutex_unlock (&mutex);
... 
相信很多人都會有這個疑問:為什么pthread_cond_wait需要的互斥鎖不在函數內部定義,而要使用戶定義的呢?現在沒有時間研究 pthread_cond_wait 的源代碼,帶著這個問題對條件變量的用法做如下猜測,希望明白真相看過源代碼的朋友不吝指正。
 
pthread_cond_wait 和 pthread_cond_timewait 函數為什么需要互斥鎖?因為:條件變量是線程同步的一種方法,這兩個函數又是等待信號的函數,函數內部一定有須要同步保護的數據。使用用戶定義的互斥鎖而不在函數內部定義的原因是:無法確定會有多少用戶使用條件變量,所以每個互斥鎖都須要動態定義,而且管理大量互斥鎖的開銷太大,使用用戶定義的即靈活又方便,符合UNIX哲學的編程風格(隨便推薦閱讀《UNIX編程哲學》這本好書!)。好了,說完了1和2,我們來自由猜測一下 pthread_cond_wait 函數的內部結構吧:
  int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex){if(沒有條件信號){(1)pthread_mutex_unlock (mutex); // 因為用戶在函數外面已經加鎖了(這是使用約定),但是在沒有信號的情況下為了讓其他線程也能等待cond,必須解鎖。(2) 阻塞當前線程,等待條件信號(當然應該是類似于中斷觸發的方式等待,而不是軟件輪詢的方式等待)... 有信號就繼續執行后面。(3) pthread_mutex_lock (mutex); // 因為用戶在函數外面要解鎖(這也是使用約定),所以要與1呼應加鎖,保證用戶感覺依然是自己加鎖、自己解鎖。}      ...} 
三、 信號量
 
 如同進程一樣,線程也可以通過信號量來實現通信,雖然是輕量級的。
 線程使用的基本信號量函數有四個:
 
  #include <semaphore.h> 
   int sem_init (sem_t *sem , int pshared, unsigned int value); 
sem - 指定要初始化的信號量;
 pshared - 信號量 sem 的共享選項,linux只支持0,表示它是當前進程的局部信號量;
 value - 信號量 sem 的初始值。
 
2 . 信號量值加1
 給參數sem指定的信號量值加1。
 
     int sem_post(sem_t *sem); 
給參數sem指定的信號量值減1。
 
     int sem_wait(sem_t *sem); 
- 如果sem所指的信號量的數值為0,函數將會等待直到有其它線程使它不再是0為止。
 
4 . 銷毀信號量
 銷毀指定的信號量。
 
  int sem_destroy(sem_t *sem); 
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>#define return_if_fail(p) if((p) == 0){printf ("[%s]:func error!\n", __func__);return;}typedef struct _PrivInfo
{sem_t s1;sem_t s2;time_t end_time;
}PrivInfo;static void info_init (PrivInfo* prifo);
static void info_destroy (PrivInfo* prifo);
static void* pthread_func_1 (PrivInfo* prifo);
static void* pthread_func_2 (PrivInfo* prifo);int main (int argc, char** argv)
{pthread_t pt_1 = 0;pthread_t pt_2 = 0;int ret = 0;PrivInfo* prifo = NULL;prifo = (PrivInfo* )malloc (sizeof (PrivInfo));if (prifo == NULL) {printf ("[%s]: Failed to malloc priv.\n");return -1;}info_init (prifo);ret = pthread_create (&pt_1, NULL, (void*)pthread_func_1, prifo);if (ret != 0) {perror ("pthread_1_create:");}ret = pthread_create (&pt_2, NULL, (void*)pthread_func_2, prifo);if (ret != 0) {perror ("pthread_2_create:");}pthread_join (pt_1, NULL);pthread_join (pt_2, NULL);info_destroy (prifo);return 0;
}static void info_init (PrivInfo* prifo)
{return_if_fail (prifo != NULL);prifo->end_time = time(NULL) + 10;sem_init (&prifo->s1, 0, 1);sem_init (&prifo->s2, 0, 0);return;
}static void info_destroy (PrivInfo* prifo)
{return_if_fail (prifo != NULL);sem_destroy (&prifo->s1);sem_destroy (&prifo->s2);free (prifo);prifo = NULL;return;
}static void* pthread_func_1 (PrivInfo* prifo)
{return_if_fail (prifo != NULL);while (time(NULL) < prifo->end_time){sem_wait (&prifo->s2);printf ("pthread1: pthread1 get the lock.\n");sem_post (&prifo->s1);printf ("pthread1: pthread1 unlock\n");sleep (1);}return;
}static void* pthread_func_2 (PrivInfo* prifo)
{return_if_fail (prifo != NULL);while (time (NULL) < prifo->end_time){sem_wait (&prifo->s1);printf ("pthread2: pthread2 get the unlock.\n");sem_post (&prifo->s2);printf ("pthread2: pthread2 unlock.\n");sleep (1);}return;} 
四 讀寫鎖
4.1 注意事項
 
- 1.如果一個線程用讀鎖鎖定了臨界區,那么其他線程也可以用讀鎖來進入臨界區,這樣就可以多個線程并行操作。但這個時候,如果再進行寫鎖加鎖就會發生阻塞,寫鎖請求阻塞后,后面如果繼續有讀鎖來請求,這些后來的讀鎖都會被阻塞!這樣避免了讀鎖長期占用資源,防止寫鎖饑餓!
 - 2.如果一個線程用寫鎖鎖住了臨界區,那么其他線程不管是讀鎖還是寫鎖都會發生阻塞!
 
4.2 常用接口
 1. 初始化:
 
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); 
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);int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout);
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock, const struct timespec *restrict abs_timeout); 
3.銷毀鎖
 
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>/* 初始化讀寫鎖 */
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
/* 全局資源 */
int global_num = 10;void err_exit(const char *err_msg)
{printf("error:%s\n", err_msg);exit(1);
}/* 讀鎖線程函數 */
void *thread_read_lock(void *arg)
{char *pthr_name = (char *)arg;while (1){/* 讀加鎖 */pthread_rwlock_rdlock(&rwlock);printf("線程%s進入臨界區,global_num = %d\n", pthr_name, global_num);sleep(1);printf("線程%s離開臨界區...\n", pthr_name);/* 讀解鎖 */pthread_rwlock_unlock(&rwlock);sleep(1);}return NULL;
}/* 寫鎖線程函數 */
void *thread_write_lock(void *arg)
{char *pthr_name = (char *)arg;while (1){/* 寫加鎖 */pthread_rwlock_wrlock(&rwlock);/* 寫操作 */global_num++;printf("線程%s進入臨界區,global_num = %d\n", pthr_name, global_num);sleep(1);printf("線程%s離開臨界區...\n", pthr_name);/* 寫解鎖 */pthread_rwlock_unlock(&rwlock);sleep(2);}return NULL;
}int main(void)
{pthread_t tid_read_1, tid_read_2, tid_write_1, tid_write_2;/* 創建4個線程,2個讀,2個寫 */if (pthread_create(&tid_read_1, NULL, thread_read_lock, "read_1") != 0)err_exit("create tid_read_1");if (pthread_create(&tid_read_2, NULL, thread_read_lock, "read_2") != 0)err_exit("create tid_read_2");if (pthread_create(&tid_write_1, NULL, thread_write_lock, "write_1") != 0)err_exit("create tid_write_1");if (pthread_create(&tid_write_2, NULL, thread_write_lock, "write_2") != 0)err_exit("create tid_write_2");/* 隨便等待一個線程,防止main結束 */if (pthread_join(tid_read_1, NULL) != 0)err_exit("pthread_join()");return 0;
}
                            
總結
                            
                                以上是生活随笔為你收集整理的线程间同步的几种方法--互斥锁,条件变量,信号量,读写锁的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                            
                                如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。