linux线程间同步(1)互斥锁与条件变量
線程的最大特點是資源的共享性,但資源共享中的同步問題是多線程編程的難點。linux下提供了多種方式來處理線程同步,最常用的是互斥鎖、條件變量和信號量以及讀寫鎖。
互斥鎖(mutex)
互斥鎖,是一種信號量,常用來防止兩個進程或線程在同一時刻訪問相同的共享資源。可以保證以下三點:
- 原子性:把一個互斥量鎖定為一個原子操作,這意味著操作系統(tǒng)(或pthread函數(shù)庫)保證了如果一個線程鎖定了一個互斥量,沒有其他線程在同一時間可以成功鎖定這個互斥量。
- 唯一性:如果一個線程鎖定了一個互斥量,在它解除鎖定之前,沒有其他線程可以鎖定這個互斥量。
- 非繁忙等待:如果一個線程已經(jīng)鎖定了一個互斥量,第二個線程又試圖去鎖定這個互斥量,則第二個線程將被掛起(不占用任何cpu資源),直到第一個線程解除對這個互斥量的鎖定為止,第二個線程則被喚醒并繼續(xù)執(zhí)行,同時鎖定這個互斥量。
從以上三點,我們看出可以用互斥量來保證對變量(關(guān)鍵的代碼段)的排他性訪問。
互斥鎖常用函數(shù)
#include <pthread.h>//初始化互斥鎖 int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr); //互斥鎖靜態(tài)賦值 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//阻塞加鎖 int pthread_mutex_lock(pthread_mutex_t *mutex); //非阻塞加鎖,成功則返回0,否則返回EBUSY int pthread_mutex_trylock(pthread_mutex_t *mutex); //解鎖 int pthread_mutex_unlock(pthread_mutex_t *mutex);//銷毀互斥鎖 int pthread_mutex_destroy(pthread_mutex_t *mutex);如果要正確的使用pthread_mutex_lock與pthread_mutex_unlock,請參考pthread_cleanup_push和pthread_cleanup_pop宏,它能夠在線程被cancel的時候正確的釋放mutex!
互斥鎖屬性
#include <pthread.h>int pthread_mutexattr_destroy(pthread_mutexattr_t *attr); int pthread_mutexattr_init(pthread_mutexattr_t *attr);//鎖的范圍:PTHREAD_PROCESS_PRIVATE(進程內(nèi)),PTHREAD_PROCESS_SHARED(進程間) int pthread_mutexattr_getpshared(const pthread_mutexattr_t *restrict attr, int *restrict pshared); int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr,int pshared);//鎖的類型 int pthread_mutexattr_gettype(const pthread_mutexattr_t *restrict attr,int *restrict type); int pthread_mutexattr_settype(pthread_mutexattr_t *attr, int type);int pthread_mutexattr_getprotocol(const pthread_mutexattr_t *restrict attr, int *restrict protocol); int pthread_mutexattr_setprotocol(pthread_mutexattr_t *attr,int protocol);int pthread_mutexattr_getprioceiling(const pthread_mutexattr_t *restrict attr, int *restrict prioceiling); int pthread_mutexattr_setprioceiling(pthread_mutexattr_t *attr,int prioceiling);說明:pthread庫不是Linux系統(tǒng)默認的庫,連接時需要使用靜態(tài)庫libpthread.a,所以在使用pthread_create()創(chuàng)建線程,以及調(diào)用pthread_atfork()函數(shù)建立fork處理程序時,需要鏈接該庫。在編譯中要加 -lpthread參數(shù)。
條件變量(cond)
利用線程間共享的全局變量進行同步的一種機制。條件變量上的基本操作有:觸發(fā)條件(當條件變?yōu)?true 時);等待條件,掛起線程直到其他線程觸發(fā)條件。
int pthread_cond_init(pthread_cond_t *cond,pthread_condattr_t *cond_attr);int pthread_cond_wait(pthread_cond_t *cond,pthread_mutex_t *mutex);int pthread_cond_timewait(pthread_cond_t *cond,pthread_mutex *mutex,const timespec *abstime);int pthread_cond_destroy(pthread_cond_t *cond);int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); //解除所有線程的阻塞使用說明:
動態(tài)初始化調(diào)用pthread_cond_init()或者pthread_cond_t cond=PTHREAD_COND_INITIALIER靜態(tài)初始化;屬性置為NULL
pthread_cond_wait與pthread_cond_timedwait,在使用前應用程序必須執(zhí)行了加鎖互斥量,兩函數(shù)在調(diào)用時自動解鎖互斥量,等待條件互斥量觸發(fā)。這時線程掛起,不占用CPU,前者直到條件變量被觸發(fā),后者等待條件變量被觸發(fā)或者超時(返回ETIMEOUT)。函數(shù)返回前,自動重新對互斥量自動加鎖。
互斥量的解鎖和在條件變量上掛起都是自動進行的。因此,在條件變量被觸發(fā)前,如果所有的線程都要對互斥量加鎖,這種機制可保證在線程加鎖互斥量和進入等待條件變量期間,條件變量不被觸發(fā)。條件變量要和互斥量相聯(lián)結(jié),以避免出現(xiàn)條件競爭— —個線程預備等待一個條件變量,當它在真正進入等待之前,另一個線程恰好觸發(fā)了該條件(條件滿足信號有可能在測試條件和調(diào)用pthread_cond_wait函數(shù)(block)之間被發(fā)出,從而造成無限制的等待)。
pthread_cond_destroy 銷毀一個條件變量,釋放它擁有的資源。進入 pthread_cond_destroy 之前,必須沒有在該條件變量上等待的線程,否則返回EBUSY
條件變量函數(shù)不是異步信號安全的,不應當在信號處理程序中進行調(diào)用。特別要注意,如果在信號處理程序中調(diào)用 pthread_cond_signal 或 pthread_cond_boardcast 函數(shù),可能導致調(diào)用線程死鎖。pthread_cond_signal與pthread_cond_broadcast無需考慮調(diào)用線程是否是mutex的擁有者,也就是說,可以在lock與unlock以外的區(qū)域調(diào)用。如果我們對調(diào)用行為不關(guān)心,那么請在lock區(qū)域之外調(diào)用吧。
代碼實例
#include <stdio.h> #include <stdlib.h> #include <pthread.h> #include <unistd.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的并發(fā)性pthread_mutex_lock(&mtx);while (head == NULL){//這個while要特別說明一下,單個pthread_cond_wait功能很完善,為何//這里要有一個while (head == NULL)呢?因為pthread_cond_wait里的線//程可能會被意外喚醒,如果這個時候head != NULL,則不是我們想要的情況。//這個時候,應該讓線程繼續(xù)進入pthread_cond_wait// pthread_cond_wait會先解除之前的pthread_mutex_lock鎖定的mtx,//然后阻塞在等待對列里休眠,直到再次被喚醒(大多數(shù)情況下是等待的條件成立//而被喚醒,喚醒后,該進程會先鎖定先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); //臨界區(qū)數(shù)據(jù)操作完畢,釋放互斥鎖}pthread_cleanup_pop(0);return 0; }int main(int argc, char *argv[]) {pthread_t tid;int i;struct node *p;//子線程會一直等待資源,類似生產(chǎn)者和消費者,但是這里的消費者可以是多個消費者,而//不僅僅支持普通的單個消費者,這個模型雖然簡單,但是很強大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");//關(guān)于pthread_cancel,有一點額外的說明,它是從外部終止子線程,子線程會在最近的取消點,退出//線程,而在我們的代碼里,最近的取消點肯定就是pthread_cond_wait()了。pthread_cancel(tid);pthread_join(tid, NULL);return 0; }線程取消點
一般情況下,線程在其主體函數(shù)退出的時候會自動終止,但同時也可以因為接收到另一個線程發(fā)來的終止(取消)請求而強制終止。
相關(guān)概念
線程取消的方法是向目標線程發(fā)送Cancel信號,但如何處理Cancel信號則由目標線程自己決定,或者忽略、或者立即終止、或者繼續(xù)運行至Cancelation-point(取消點),由不同的Cancelation狀態(tài)決定。
線程接收到CANCEL信號的缺省處理(即pthread_create()創(chuàng)建線程的缺省狀態(tài))是繼續(xù)運行至取消點,也就是說設置一個CANCELED狀態(tài),線程繼續(xù)運行,只有運行至Cancelation-point的時候才會退出。
根據(jù)POSIX標準,pthread_join()、pthread_testcancel()、pthread_cond_wait()、 pthread_cond_timedwait()、sem_wait()、sigwait()等函數(shù)以及read()、write()等會引起阻塞的系統(tǒng)調(diào)用都是Cancelation-point,而其他pthread函數(shù)都不會引起Cancelation動作。但是pthread_cancel的手冊頁聲稱,由于LinuxThread庫與C庫結(jié)合得不好,因而目前C庫函數(shù)都不是Cancelation-point;但CANCEL信號會使線程從阻塞的系統(tǒng)調(diào)用中退出,并置EINTR錯誤碼,因此可以在需要作為Cancelation-point的系統(tǒng)調(diào)用前后調(diào)用 pthread_testcancel(),從而達到POSIX標準所要求的目標.
相關(guān)API
取消線程運行
int pthread_cancel(pthread_t thread);發(fā)送終止信號給thread線程,成功則返回0,否則返回非0. 成功發(fā)送并不意味著thread會終止.
設置取消點
如果線程處于無限循環(huán)中,且循環(huán)體內(nèi)沒有執(zhí)行至取消點的必然路徑,則線程無法由外部其他線程的取消請求而終止。因此在這樣的循環(huán)體的必經(jīng)路徑上應該加入pthread_testcancel()調(diào)用。
pthread_testcancel:
- 設置取消點
- 檢查本線程是否處于Canceld狀態(tài),如果是,則進行取消動作,否則直接返回。
設置線程取消狀態(tài)與類型
int pthread_setcancelstate(int state, int *oldstate); int pthread_setcanceltype(int type, int *oldtype);pthread_setcancelstate設置線程對Cancel的反應,PTHREAD_CANCEL_ENABLE(default)與PTHREAD_CANCEL_DISABLE分別表示接受信號后設為CANCEL轉(zhuǎn)態(tài)或者忽略CANCEL信號繼續(xù)運行
pthread_setcanceltype設置本線程取消動作的執(zhí)行時機,PTHREAD_CANCEL_DEFFERED(default)和PTHREAD_CANCEL_ASYCHRONOUS,僅當Cancel狀態(tài)為Enable時有效,分別表示收到信號后繼續(xù)運行至下一個取消點再退出和立即執(zhí)行取消動作(退出).
代碼實例
#include <stdio.h> #include <errno.h> #include <unistd.h> #include <stdlib.h> #include <pthread.h>#define THREAD_MAX 4pthread_mutex_t mutex; pthread_t thread[THREAD_MAX];static int tries; static int started;void print_it(int *arg) {pthread_t tid;tid = pthread_self();printf("Thread %lx was canceled on its %d try.\n",tid,*arg); }void *Search_Num(int arg) {pthread_t tid;int num;int k=0,h=0,j;int ntries;tid = pthread_self();srand(arg);num = rand()&0xFFFFFF;printf("thread num %lx\n",tid);ntries = 0;//默認設置//pthread_setcancelstate(PTHREAD_CANCEL_ENABLE,NULL);//pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED,NULL);pthread_cleanup_push((void *)print_it,(void *)&ntries);while(1) {num = (num+1)&0xffffff;ntries++;if(arg == num){//只允許一個線程操作此處while(pthread_mutex_trylock(&mutex) == EBUSY) { //一個線程操作后其余線程進入次循環(huán)掛起,等待pthread_cancel函數(shù)發(fā)送cancel信號終止線程k++;if(k == 100) {printf("----------2busy2-----------\n");}pthread_testcancel();}tries = ntries;//pthread_mutex_unlock(&mutex); //如果加上這句話,將會有好幾個線程找到主函數(shù)中設定的值pidprintf("Thread %lx found the number!\n",tid);for(j = 0;j<THREAD_MAX;j++) {if(thread[j] != tid) {pthread_cancel(thread[j]);}}break;}if (ntries % 100 == 0) {h++;/*線程阻塞,其他線程爭奪資源,或者是等待pthread_cancel函數(shù)發(fā)送cancel信號終止線程*/pthread_testcancel();/*這是為了弄明白pthread_testcancel函數(shù)的作用而設置的代碼段*/if(h == 10000){h = 0;printf("----------thread num %lx-------------\n",tid);}}}pthread_cleanup_pop(0);return (void *)0; }int main() {int i,pid;pid = getpid(); //設置要查找的數(shù)pthread_mutex_init(&mutex,NULL);printf("Search the num of %d\n",pid);for (started = 0; started < THREAD_MAX; started++) {pthread_create(&thread[started],NULL,(void *)Search_Num,(void *)pid);}for (i = 0; i < THREAD_MAX; i++) {pthread_join(thread[i],NULL);}printf("It took %d tries ot find the number!\n",tries);return 0; }參考:
- 多線程編程之線程取消
總結(jié)
以上是生活随笔為你收集整理的linux线程间同步(1)互斥锁与条件变量的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: eclipse--python开发环境搭
- 下一篇: Linux网络编程--文件描述符