經過這篇文字,您將可以解答以下問題:javascript
如何來標識一個線程?php
如何建立一個新線程?html
如何實現單個線程的退出?java
什麼狀況會發生線程死鎖,如何避免死鎖?shell
讀寫鎖的使用方法。數據庫
1. 如何來標識一個線程?
表示進程號的爲pid_t類型,表示線程號的是pthread_t類型。pthread_t是一個結構體而不是整型。
使用pthread_equal肯定兩個線程號是否相等:
#includeint pthread_equal(pthread_t tid1, pthread_t tid2);Returns: nonzero if equal, 0 otherwise
使用pthread_self函數來獲取線程的ID:
#includepthread_t pthread_self(void);Returns: the thread ID of the calling thread
使用pthread_create函數建立一個新線程。
如下是代碼片斷: #include int pthread_create(pthread_t *restrict tidp, const pthread_attr_t *restrict attr, void *(*start_rtn)(void), void *restrict arg); Returns: 0 if OK, error number on failure |
當該函數成功返回的時候,tidp所指向的內存位置將被分配給新建立的帶有thread ID的線程。
attr用來定製各類線程參數。
新建立的線程將在start_rtn函數所指向的地址開始運行,該函數接受一個參數無類型的指針arg做爲參數
線程建立時沒法保證哪一個線程會先運行。新建立的線程能夠訪問進程地址空間,而且繼承了調用線程的浮點環境以及信號量掩碼,但對於線程的未決信號量也將會被清除。
下面的這段程序建立新的線程,並打印線程id,新線程經過pthread_self函數獲取本身的線程ID。
#include "apue.h" #includepthread_t ntid;void printids(const char *s){ pid_t pid; pthread_t tid; pid = getpid(); tid = pthread_self(); printf("%s pid %u tid %u (0x%x)\\n", s, (unsigned int)pid, (unsigned int)tid, (unsigned int)tid);}void * thr_fn(void *arg){ printids("new thread: "); return((void *)0);}int main(void){ int err; err = pthread_create(&ntid, NULL, thr_fn, NULL); if (err != 0) err_quit("can\'t create thread: %s\\n", strerror(err)); printids("main thread:"); sleep(1); exit(0);}
若是一個線程調用了exit, _Exit, 或者_exit,將致使整個進程的終止。要實現單個線程的退出,能夠採用以下方式:
o 線程能夠簡單的從start routine返回,返回值就是線程的退出代碼。
o 線程能夠被同一進程中的其它線程終止。
o 線程調用pthread_exit
#includevoid pthread_exit(void *rval_ptr);
4.如何使調用線程阻塞等待指定線程的退出,並得到退出線程的返回碼?
#includeint pthread_join(pthread_t thread, void **rval_ptr);Returns: 0 if OK, error number on failure
調用線程將會被阻塞直到指定的線程終止。若是線程簡單的從start routine返回則rval_ptr將包含返回代碼。若是線程是被撤銷(調用pthread_exit)的,rval_ptr指向的內存地址將被設置爲PTHREAD_CANCELED.
經過調用pthread_join,咱們自動的將一個線程變成分離狀態,這樣就能夠實現資源的回收。若是線程已經處於分離狀態,調用pthread_join將會失敗,並返回EINVAL。
若是咱們對於線程的返回值不感興趣,能夠將rval_ptr設置成NULL。
一段有缺陷的代碼:
#include "apue.h" #includestruct foo { int a, b, c, d;};voidprintfoo(const char *s, const struct foo *fp){ printf(s); printf(" structure at 0x%x\\n", (unsigned)fp); printf(" foo.a = %d\\n", fp->a); printf(" foo.b = %d\\n", fp->b); printf(" foo.c = %d\\n", fp->c); printf(" foo.d = %d\\n", fp->d);}void *thr_fn1(void *arg){ struct foo foo = {1, 2, 3, 4}; printfoo("thread 1:\\n", &foo); pthread_exit((void *)&foo);}void *thr_fn2(void *arg){ printf("thread 2: ID is %d\\n", pthread_self()); pthread_exit((void *)0);}intmain(void){ int err; pthread_t tid1, tid2; struct foo *fp; err = pthread_create(&tid1, NULL, thr_fn1, NULL); if (err != 0) err_quit("can\'t create thread 1: %s\\n", strerror(err)); err = pthread_join(tid1, (void *)&fp); if (err != 0) err_quit("can\'t join with thread 1: %s\\n", strerror(err)); sleep(1); printf("parent starting second thread\\n"); err = pthread_create(&tid2, NULL, thr_fn2, NULL); if (err != 0) err_quit("can\'t create thread 2: %s\\n", strerror(err)); sleep(1); printfoo("parent:\\n", fp); exit(0);}
注意,pthread_create 和 pthread_exit函數的無類型指針能夠傳遞複雜的結構信息,但這個結構所使用的內存在調用者完成後必須仍然有效(分配在堆上或者是靜態變量),不然就會出現使用無效的錯誤。這段代碼中thr_fn1函數中變量foo分配在棧上,但該線程退出後,主線程經過pthread_join獲取foo的地址並進行操做(調用printfoo函數時)就會出現錯誤,由於此時thr_fn1已經退出它的棧已經被銷燬。
調用pthread_cancel函數將致使tid所指向的線程終止運行。可是,一個線程能夠選擇忽略其它線程控制該線程什麼時候退出。注意,該函數並不等待線程終止,它僅僅提出要求。
#includeint pthread_cancel(pthread_t tid);Returns: 0 if OK, error number on failure
線程能夠創建多個清理處理程序,這些程序記錄在棧中,也就是說他們的執行順序與註冊順序想法。使用以下函數註冊清理函數:
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
rtn將被調用,並傳以arg參數,引發該函數調用的狀況以下:
o 調用pthread_exit
o 對於退出請求的反應
o 以非0參數調用pthread_cleanup_push
若是pthread_cleanup_pop的參數非0則僅僅移除該處理函數而不執行。
若是函數已經處於分離狀態,則當它退出時線程底層的存儲資源會被當即回收。處於分離狀態的線程,若是調用pthread_join來等待其退出將會出現錯誤。
經過下列函數可讓進程處於分離狀態:
#includeint pthread_detach(pthread_t tid);Returns: 0 if OK, error number on failure
使用pthreads mutual-exclusion interfaces。引入了mutex,用pthread_mutex_t類型來表示。在使用這個變量以前,咱們首先要將其初始化,或者賦值爲PTHREAD_MUTEX_INITIALIZER(僅僅用於靜態分配的mutexs),或者調用pthread_mutex_init。若是咱們動態的爲mutex分配空間(例如經過調用malloc),咱們須要在調用free釋放內存以前調用pthread_mutex_destroy。
函數定義以下:
如下是代碼片斷: #include int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); int pthread_mutex_destroy(pthread_mutex_t *mutex); Both return: 0 if OK, error number on failure |
初始化mutex時參數attr用來指定mutex的屬性,要使用默認值將它設置爲NULL。
使用以下函數對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
注意當mutex已經被加鎖則 pthread_mutex_lock會阻塞。若是一個線程沒法忍受阻塞,能夠調用pthread_mutex_trylock來加鎖,加鎖失敗則當即返回EBUSY。
若是一個線程對mutex加兩次鎖則顯然會致使死鎖。但實際上死鎖的狀況要複雜的多:when we use more than one mutex in our programs, a deadlock can occur if we allow one thread to hold a mutex and block while trying to lock a second mutex at the same time that another thread holding the second mutex tries to lock the first mutex. Neither thread can proceed, because each needs a resource that is held by the other, so we have a deadlock.
死鎖能夠經過控制加鎖的順序來避免。有兩個mutex A和B,若是全部的線程老是先對A加鎖再對B加鎖就不會產生死鎖。但實際應用中可能很難保證這種順序加鎖的方式,這種狀況下,可使用pthread_mutex_trylock來避免死鎖的發生。
讀寫鎖的初始化與銷燬:
如下是代碼片斷: #include int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr); int pthread_rwlock_destroy(pthread_rwlock_t *rwlock); Both return: 0 if OK, error number on failure |
加鎖與解鎖:
#includeint pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);All return: 0 if OK, error number on failure
對於讀者的數量會有限制,所以調用 pthread_rwlock_rdlock時須要檢查返回值。
在正確使用的狀況下,不須要檢查pthread_rwlock_wrlock和pthread_rwlock_unlock的返回值。
條件加鎖:
#includeint pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);Both return: 0 if OK, error number on failure
條件變量是線程可用的另一種同步機制。條件變量給多個線程提供了一個會合的場所。條件變量與互斥量一塊兒使用時,容許線程以無競爭的方式等待特定條件的發生。條件自己是由互斥量保護的。線程在改變狀態前必須首先鎖住互斥量,其它線程在得到互斥量以前不會覺察到這種變化。
條件變量的類型爲pthread_cond_t ,其初始化與銷燬的方式與mutex相似,注意靜態變量能夠經過指定常量PTHREAD_COND_INITIALIZER來進行初始化。
#includeint pthread_cond_init(pthread_cond_t *restrict cond, pthread_condattr_t *restrict attr);int pthread_cond_destroy(pthread_cond_t *cond);Both return: 0 if OK, error number on failure
使用pthread_cond_wait來等待條件變成真。
函數定義以下:
如下是代碼片斷: #include 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); Both return: 0 if OK, error number on failure |
注意,調用成功返回後,線程須要從新計算條件變量,由於其它線程可能已經改變了條件。
有兩個函數用於通知線程一個條件已經被知足。pthread_cond_signal函數用來喚醒一個等待條件知足的線程, pthread_cond_broadcast用來喚醒全部等待條件知足的線程。
他們的定義爲:
#includeint pthread_cond_signal(pthread_cond_t *cond);int pthread_cond_broadcast(pthread_cond_t *cond);Both return: 0 if OK, error number on failure
下面的這段代碼實現了相似於生產者消費者模型的程序,生產者經過enqueue_msg將消息放入隊列,併發送信號通知給消費者線程。消費者線程被喚醒而後處理消息。
#includestruct 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;voidprocess_msg(void){ struct msg *mp; for (;;) { pthread_mutex_lock(&qlock); while (workq == NULL) pthread_cond_wait(&qready, &qlock); /*get msg from the queue*/ mp = workq; workq = mp->m_next; pthread_mutex_unlock(&qlock); /* now process the message mp */ }}voidenqueue_msg(struct msg *mp){ pthread_mutex_lock(&qlock); /*put msg in queue*/ mp->m_next = workq; workq = mp; pthread_mutex_unlock(&qlock); pthread_cond_signal(&qready);}
在pthread_cond_signal發送消息以前並不須要佔用鎖,由於一旦線程被喚醒後經過while發現沒有要處理的msg存在則會再次陷入睡眠。若是系統不能容忍這種競爭環境,則須要在unlock以前調用cond_signal,可是在多處理器機器上,這樣會致使多線程被喚醒而後當即進入阻塞(cond_signal喚醒線程,但因爲咱們仍佔用着鎖,因此這些線程又會當即阻塞)。
Linux操做系統下的多線程編程詳細解析----條件變量
1.初始化條件變量pthread_cond_init
#include <pthread.h>
int pthread_cond_init(pthread_cond_t *cv,
const pthread_condattr_t *cattr);
返回值:函數成功返回0;任何其餘返回值都表示錯誤
初始化一個條件變量。當參數cattr爲空指針時,函數建立的是一個缺省的條件變量。不然條件變量的屬性將由cattr中的屬性值來決定。調用 pthread_cond_init函數時,參數cattr爲空指針等價於cattr中的屬性爲缺省屬性,只是前者不須要cattr所佔用的內存開銷。這個函數返回時,條件變量被存放在參數cv指向的內存中。
能夠用宏PTHREAD_COND_INITIALIZER來初始化靜態定義的條件變量,使其具備缺省屬性。這和用pthread_cond_init函數動態分配的效果是同樣的。初始化時不進行錯誤檢查。如:
pthread_cond_t cv = PTHREAD_COND_INITIALIZER;
不能由多個線程同時初始化一個條件變量。當須要從新初始化或釋放一個條件變量時,應用程序必須保證這個條件變量未被使用。
2.阻塞在條件變量上pthread_cond_wait
#include <pthread.h>
int pthread_cond_wait(pthread_cond_t *cv,
pthread_mutex_t *mutex);
返回值:函數成功返回0;任何其餘返回值都表示錯誤
函數將解鎖mutex參數指向的互斥鎖,並使當前線程阻塞在cv參數指向的條件變量上。
被阻塞的線程能夠被pthread_cond_signal函數,pthread_cond_broadcast函數喚醒,也可能在被信號中斷後被喚醒。
pthread_cond_wait函數的返回並不意味着條件的值必定發生了變化,必須從新檢查條件的值。
pthread_cond_wait函數返回時,相應的互斥鎖將被當前線程鎖定,即便是函數出錯返回。
通常一個條件表達式都是在一個互斥鎖的保護下被檢查。當條件表達式未被知足時,線程將仍然阻塞在這個條件變量上。當另外一個線程改變了條件的值並向條件變量發出信號時,等待在這個條件變量上的一個線程或全部線程被喚醒,接着都試圖再次佔有相應的互斥鎖。
阻塞在條件變量上的線程被喚醒之後,直到pthread_cond_wait()函數返回以前條件的值都有可能發生變化。因此函數返回之後,在鎖定相應的互斥鎖以前,必須從新測試條件值。最好的測試方法是循環調用pthread_cond_wait函數,並把知足條件的表達式置爲循環的終止條件。如:
pthread_mutex_lock();
while (condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
阻塞在同一個條件變量上的不一樣線程被釋放的次序是不必定的。
注意:pthread_cond_wait()函數是退出點,若是在調用這個函數時,已有一個掛起的退出請求,且線程容許退出,這個線程將被終止並開始執行善後處理函數,而這時和條件變量相關的互斥鎖仍將處在鎖定狀態。
3.解除在條件變量上的阻塞pthread_cond_signal
#include <pthread.h>
int pthread_cond_signal(pthread_cond_t *cv);
返回值:函數成功返回0;任何其餘返回值都表示錯誤
函數被用來釋放被阻塞在指定條件變量上的一個線程。
必須在互斥鎖的保護下使用相應的條件變量。不然對條件變量的解鎖有可能發生在鎖定條件變量以前,從而形成死鎖。
喚醒阻塞在條件變量上的全部線程的順序由調度策略決定,若是線程的調度策略是SCHED_OTHER類型的,系統將根據線程的優先級喚醒線程。
若是沒有線程被阻塞在條件變量上,那麼調用pthread_cond_signal()將沒有做用。
4.阻塞直到指定時間pthread_cond_timedwait
#include <pthread.h>
#include <time.h>
int pthread_cond_timedwait(pthread_cond_t *cv,
pthread_mutex_t *mp, const structtimespec * abstime);
返回值:函數成功返回0;任何其餘返回值都表示錯誤
函數到了必定的時間,即便條件未發生也會解除阻塞。這個時間由參數abstime指定。函數返回時,相應的互斥鎖每每是鎖定的,即便是函數出錯返回。
注意:pthread_cond_timedwait函數也是退出點。
超時時間參數是指一天中的某個時刻。使用舉例:
pthread_timestruc_t to;
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;
超時返回的錯誤碼是ETIMEDOUT。
5.釋放阻塞的全部線程pthread_cond_broadcast
#include <pthread.h>
int pthread_cond_broadcast(pthread_cond_t *cv);
返回值:函數成功返回0;任何其餘返回值都表示錯誤
函數喚醒全部被pthread_cond_wait函數阻塞在某個條件變量上的線程,參數cv被用來指定這個條件變量。當沒有線程阻塞在這個條件變量上時,pthread_cond_broadcast函數無效。
因爲pthread_cond_broadcast函數喚醒全部阻塞在某個條件變量上的線程,這些線程被喚醒後將再次競爭相應的互斥鎖,因此必須當心使用pthread_cond_broadcast函數。
6.釋放條件變量pthread_cond_destroy
#include <pthread.h>
int pthread_cond_destroy(pthread_cond_t *cv);
返回值:函數成功返回0;任何其餘返回值都表示錯誤
釋放條件變量。
注意:條件變量佔用的空間並未被釋放。
7.喚醒丟失問題
在線程未得到相應的互斥鎖時調用pthread_cond_signal或pthread_cond_broadcast函數可能會引發喚醒丟失問題。
喚醒丟失每每會在下面的狀況下發生:
轉載聲明: 本文轉自 http://pzs1237.blog.163.com/blog/static/29813006200952335454934/
===============================================================================
條件鎖pthread_cond_t
說明,
等待線程
1。使用pthread_cond_wait前要先加鎖
2。pthread_cond_wait內部會解鎖,而後等待條件變量被其它線程激活
3。pthread_cond_wait被激活後會再自動加鎖
激活線程:
1。加鎖(和等待線程用同一個鎖)
2。pthread_cond_signal發送信號
3。解鎖
激活線程的上面三個操做在運行時間上都在等待線程的pthread_cond_wait函數內部。
程序示例:
運行結果:
[work@db-testing-com06-vm3.db01.baidu.com pthread]$ gcc -o pthread_cond pthread_cond.c -lpthread
[work@db-testing-com06-vm3.db01.baidu.com pthread]$ ./pthread_cond
decrement_count get count_lock
decrement_count count == 0
decrement_count before cond_wait
increment_count get count_lock
increment_count before cond_signal
increment_count after cond_signal
decrement_count after cond_wait
轉載聲明: 本文轉自 http://egeho123.blogbus.com/logs/10821816.html
===============================================================================
多線程編程,條件變量pthread_cond_t應用
程序代碼:
運行結果:
[work@db-testing-com06-vm3.db01.baidu.com pthread]$ gcc -o pthread_cond2 pthread_cond2.c -lpthread
[work@db-testing-com06-vm3.db01.baidu.com pthread]$ ./pthread_cond2
counter: 0
counter: 0
counter: 1
counter: 2
counter: 3
counter: 4
counter: 5
counter: 6
counter: 7
counter: 8
counter: 9
調試程序的運行過程:
注:更清晰的運行流程請詳見以下「改進代碼」
條件變量函數
操做 |
相關函數說明 |
---|---|
初始化條件變量 |
pthread_cond_init 語法 |
基於條件變量阻塞 |
pthread_cond_wait 語法 |
解除阻塞特定線程 |
pthread_cond_signal 語法 |
在指定的時間以前阻塞 |
pthread_cond_timedwait 語法 |
在指定的時間間隔內阻塞 |
pthread_cond_reltimedwait_np 語法 |
解除阻塞全部線程 |
pthread_cond_broadcast 語法 |
銷燬條件變量狀態 |
pthread_cond_destroy 語法 |
使用 pthread_cond_init(3C) 能夠將 cv 所指示的條件變量初始化爲其缺省值,或者指定已經使用 pthread_condattr_init() 設置的條件變量屬性。
int pthread_cond_init(pthread_cond_t *cv,
const pthread_condattr_t *cattr);
#include <pthread.h>
pthread_cond_t cv;
pthread_condattr_t cattr;
int ret;
/* initialize a condition variable to its default value */
ret = pthread_cond_init(&cv, NULL);
/* initialize a condition variable */
ret = pthread_cond_init(&cv, &cattr);
cattr 設置爲 NULL。將 cattr 設置爲 NULL 與傳遞缺省條件變量屬性對象的地址等效,可是沒有內存開銷。對於 Solaris 線程,請參見cond_init 語法。
使用 PTHREAD_COND_INITIALIZER 宏能夠將以靜態方式定義的條件變量初始化爲其缺省屬性。PTHREAD_COND_INITIALIZER 宏與動態分配具備 null 屬性的 pthread_cond_init() 等效,可是不進行錯誤檢查。
多個線程決不能同時初始化或從新初始化同一個條件變量。若是要從新初始化或銷燬某個條件變量,則應用程序必須確保該條件變量未被使用。
pthread_cond_init() 在成功完成以後會返回零。其餘任何返回值都表示出現了錯誤。若是出現如下任一狀況,該函數將失敗並返回對應的值。
EINVAL
描述:
cattr 指定的值無效。
EBUSY
描述:
條件變量處於使用狀態。
EAGAIN
描述:
必要的資源不可用。
ENOMEM
描述:
內存不足,沒法初始化條件變量。
使用 pthread_cond_wait(3C) 能夠以原子方式釋放 mp 所指向的互斥鎖,並致使調用線程基於 cv 所指向的條件變量阻塞。對於 Solaris 線程,請參見cond_wait 語法。
int pthread_cond_wait(pthread_cond_t *cv,pthread_mutex_t *mutex);
#include <pthread.h>
pthread_cond_t cv;
pthread_mutex_t mp;
int ret;
/* wait on condition variable */
ret = pthread_cond_wait(&cv, &mp);
阻塞的線程能夠經過 pthread_cond_signal() 或 pthread_cond_broadcast() 喚醒,也能夠在信號傳送將其中斷時喚醒。
不能經過 pthread_cond_wait() 的返回值來推斷與條件變量相關聯的條件的值的任何變化。必須從新評估此類條件。
pthread_cond_wait() 例程每次返回結果時調用線程都會鎖定而且擁有互斥鎖,即便返回錯誤時也是如此。
該條件得到信號以前,該函數一直被阻塞。該函數會在被阻塞以前以原子方式釋放相關的互斥鎖,並在返回以前以原子方式再次獲取該互斥鎖。
一般,對條件表達式的評估是在互斥鎖的保護下進行的。若是條件表達式爲假,線程會基於條件變量阻塞。而後,當該線程更改條件值時,另外一個線程會針對條件變量發出信號。這種變化會致使全部等待該條件的線程解除阻塞並嘗試再次獲取互斥鎖。
必須從新測試致使等待的條件,而後才能從 pthread_cond_wait() 處繼續執行。喚醒的線程從新獲取互斥鎖並從 pthread_cond_wait() 返回以前,條件可能會發生變化。等待線程可能並未真正喚醒。建議使用的測試方法是,將條件檢查編寫爲調用 pthread_cond_wait() 的 while() 循環。
pthread_mutex_lock();
while(condition_is_false)
pthread_cond_wait();
pthread_mutex_unlock();
若是有多個線程基於該條件變量阻塞,則沒法保證按特定的順序獲取互斥鎖。
注 –
pthread_cond_wait() 是取消點。若是取消處於暫掛狀態,而且調用線程啓用了取消功能,則該線程會終止,並在繼續持有該鎖的狀況下開始執行清除處理程序。
pthread_cond_wait() 在成功完成以後會返回零。其餘任何返回值都表示出現了錯誤。若是出現如下狀況,該函數將失敗並返回對應的值。
EINVAL
描述:
cv 或 mp 指定的值無效。
對於基於 cv 所指向的條件變量阻塞的線程,使用 pthread_cond_signal(3C) 能夠解除阻塞該線程。對於 Solaris 線程,請參見cond_signal 語法。
int pthread_cond_signal(pthread_cond_t *cv);
#include <pthread.h>
pthread_cond_t cv;
int ret;
/* one condition variable is signaled */
ret = pthread_cond_signal(&cv);
應在互斥鎖的保護下修改相關條件,該互斥鎖用於得到信號的條件變量中。不然,可能在條件變量的測試和 pthread_cond_wait() 阻塞之間修改該變量,這會致使無限期等待。
調度策略可肯定喚醒阻塞線程的順序。對於 SCHED_OTHER,將按優先級順序喚醒線程。
若是沒有任何線程基於條件變量阻塞,則調用 pthread_cond_signal() 不起做用。
pthread_mutex_t count_lock;
pthread_cond_t count_nonzero;
unsigned count;
decrement_count()
{
pthread_mutex_lock(&count_lock);
while (count == 0)
pthread_cond_wait(&count_nonzero, &count_lock);
count = count - 1;
pthread_mutex_unlock(&count_lock);
}
increment_count()
{
pthread_mutex_lock(&count_lock);
if (count == 0)
pthread_cond_signal(&count_nonzero);
count = count + 1;
pthread_mutex_unlock(&count_lock);
}
pthread_cond_signal() 在成功完成以後會返回零。其餘任何返回值都表示出現了錯誤。若是出現如下狀況,該函數將失敗並返回對應的值。
EINVAL
描述:
cv 指向的地址非法。
說明了如何使用 pthread_cond_wait() 和 pthread_cond_signal()。
pthread_cond_timedwait(3C) 的用法與 pthread_cond_wait() 的用法基本相同,區別在於在由 abstime 指定的時間以後 pthread_cond_timedwait() 再也不被阻塞。
int pthread_cond_timedwait(pthread_cond_t *cv,
pthread_mutex_t *mp, const struct timespec *abstime);
#include <pthread.h>
#include <time.h>
pthread_cond_t cv;
pthread_mutex_t mp;
timestruct_t abstime;
int ret;
/* wait on condition variable */
ret = pthread_cond_timedwait(&cv, &mp, &abstime);
pthread_cond_timewait() 每次返回時調用線程都會鎖定而且擁有互斥鎖,即便 pthread_cond_timedwait() 返回錯誤時也是如此。 對於 Solaris 線程
pthread_cond_timedwait() 函數會一直阻塞,直到該條件得到信號,或者最後一個參數所指定的時間已過爲止。
注 –
pthread_cond_timedwait() 也是取消點。
pthread_timestruc_t to;
pthread_mutex_t m;
pthread_cond_t c;
...
pthread_mutex_lock(&m);
to.tv_sec = time(NULL) + TIMEOUT;
to.tv_nsec = 0;
while (cond == FALSE) {
err = pthread_cond_timedwait(&c, &m, &to);
if (err == ETIMEDOUT) {
/* timeout, do something */
break;
}
}
pthread_mutex_unlock(&m);
pthread_cond_timedwait() 在成功完成以後會返回零。其餘任何返回值都表示出現了錯誤。若是出現如下任一狀況,該函數將失敗並返回對應的值。
EINVAL
描述:
cv 或 abstime 指向的地址非法。
ETIMEDOUT
描述:
abstime 指定的時間已過。
超時會指定爲當天時間,以便在不從新計算值的狀況下高效地從新測試條件,如示例 4–9 中所示。
pthread_cond_reltimedwait_np(3C) 的用法與 pthread_cond_timedwait() 的用法基本相同,惟一的區別在於 pthread_cond_reltimedwait_np() 會採用相對時間間隔而不是未來的絕對時間做爲其最後一個參數的值。
int pthread_cond_reltimedwait_np(pthread_cond_t *cv,
pthread_mutex_t *mp,
const struct timespec *reltime);
#include <pthread.h>
#include <time.h>
pthread_cond_t cv;
pthread_mutex_t mp;
timestruct_t reltime;
int ret;
/* wait on condition variable */
ret = pthread_cond_reltimedwait_np(&cv, &mp, &reltime);
pthread_cond_reltimedwait_np() 每次返回時調用線程都會鎖定而且擁有互斥鎖,即便 pthread_cond_reltimedwait_np() 返回錯誤時也是如此。對於 Solaris 線程,請參見 cond_reltimedwait(3C)。pthread_cond_reltimedwait_np() 函數會一直阻塞,直到該條件得到信號,或者最後一個參數指定的時間間隔已過爲止。
注 –
pthread_cond_reltimedwait_np() 也是取消點。
pthread_cond_reltimedwait_np() 在成功完成以後會返回零。其餘任何返回值都表示出現了錯誤。若是出現如下任一狀況,該函數將失敗並返回對應的值。
EINVAL
描述:
cv 或 reltime 指示的地址非法。
ETIMEDOUT
描述:
reltime 指定的時間間隔已過。
對於基於 cv 所指向的條件變量阻塞的線程,使用 pthread_cond_broadcast(3C) 能夠解除阻塞全部這些線程,這由 pthread_cond_wait() 來指定。
int pthread_cond_broadcast(pthread_cond_t *cv);
#include <pthread.h>
pthread_cond_t cv;
int ret;
/* all condition variables are signaled */
ret = pthread_cond_broadcast(&cv);
若是沒有任何線程基於該條件變量阻塞,則調用 pthread_cond_broadcast() 不起做用。對於 Solaris 線程,請參見cond_broadcast 語法。
因爲 pthread_cond_broadcast() 會致使全部基於該條件阻塞的線程再次爭用互斥鎖,所以請謹慎使用 pthread_cond_broadcast()。例如,經過使用pthread_cond_broadcast(),線程可在資源釋放後爭用不一樣的資源量,如示例 4–10 中所示。
pthread_mutex_t rsrc_lock;
pthread_cond_t rsrc_add;
unsigned int resources;
get_resources(int amount)
{
pthread_mutex_lock(&rsrc_lock);
while (resources < amount) {
pthread_cond_wait(&rsrc_add, &rsrc_lock);
}
resources -= amount;
pthread_mutex_unlock(&rsrc_lock);
}
add_resources(int amount)
{
pthread_mutex_lock(&rsrc_lock);
resources += amount;
pthread_cond_broadcast(&rsrc_add);
pthread_mutex_unlock(&rsrc_lock);
}
請注意,在 add_resources() 中,首先更新 resources 仍是首先在互斥鎖中調用 pthread_cond_broadcast() 可有可無。
應在互斥鎖的保護下修改相關條件,該互斥鎖用於得到信號的條件變量中。不然,可能在條件變量的測試和 pthread_cond_wait() 阻塞之間修改該變量,這會致使無限期等待。
pthread_cond_broadcast() 在成功完成以後會返回零。其餘任何返回值都表示出現了錯誤。若是出現如下狀況,該函數將失敗並返回對應的值。
EINVAL
描述:
cv 指示的地址非法。
使用 pthread_cond_destroy(3C) 能夠銷燬與 cv 所指向的條件變量相關聯的任何狀態。對於 Solaris 線程,請參見cond_destroy 語法。
int pthread_cond_destroy(pthread_cond_t *cv);
#include <pthread.h>
pthread_cond_t cv;
int ret;
/* Condition variable is destroyed */
ret = pthread_cond_destroy(&cv);
請注意,沒有釋放用來存儲條件變量的空間。
pthread_cond_destroy() 在成功完成以後會返回零。其餘任何返回值都表示出現了錯誤。若是出現如下狀況,該函數將失敗並返回對應的值。
EINVAL
描述:
cv 指定的值無效。
注意:pthread_cond_destroy 銷燬一個條件變量,釋放它擁有的資源。進入 pthread_cond_destroy 以前,必須沒有在該條件變量上等待的線程。
Attempting to destroy a condition variable upon which other threads are currently blocked results in undefined behavior.
若是線程未持有與條件相關聯的互斥鎖,則調用 pthread_cond_signal() 或 pthread_cond_broadcast() 會產生喚醒丟失錯誤。
知足如下全部條件時,即會出現喚醒丟失問題:
一個線程調用 pthread_cond_signal() 或 pthread_cond_broadcast()
沒有正在等待的線程
信號不起做用,所以將會丟失
僅當修改所測試的條件但未持有與之相關聯的互斥鎖時,纔會出現此問題。只要僅在持有關聯的互斥鎖同時修改所測試的條件,便可調用 pthread_cond_signal() 和pthread_cond_broadcast(),而不管這些函數是否持有關聯的互斥鎖。
併發編程中收集了許多標準的衆所周知的問題,生成方和使用者問題只是其中的一個問題。此問題涉及到一個大小限定的緩衝區和兩類線程(生成方和使用者),生成方將項放入緩衝區中,而後使用者從緩衝區中取走項。
生成方必須在緩衝區中有可用空間以後才能向其中放置內容。使用者必須在生成方向緩衝區中寫入以後才能從中提取內容。
條件變量表示一個等待某個條件得到信號的線程隊列。
示例 4–11 中包含兩個此類隊列。一個隊列 (less) 針對生成方,用於等待緩衝區中出現空位置。另外一個隊列 (more) 針對使用者,用於等待從緩衝槽位的空位置中提取其中包含的信息。該示例中還包含一個互斥鎖,由於描述該緩衝區的數據結構一次只能由一個線程訪問。
typedef struct {
char buf[BSIZE];
int occupied;
int nextin;
int nextout;
pthread_mutex_t mutex;
pthread_cond_t more;
pthread_cond_t less;
} buffer_t;
buffer_t buffer;
如示例 4–12 中所示,生成方線程獲取該互斥鎖以保護 buffer 數據結構,而後,緩衝區肯定是否有空間可用於存放所生成的項。若是沒有可用空間,生成方線程會調用pthread_cond_wait()。pthread_cond_wait() 會致使生成方線程鏈接正在等待 less 條件得到信號的線程隊列。less 表示緩衝區中的可用空間。
與此同時,在調用 pthread_cond_wait() 的過程當中,該線程會釋放互斥鎖的鎖定。正在等待的生成方線程依賴於使用者線程在條件爲真時發出信號,如示例 4–12 中所示。該條件得到信號時,將會喚醒等待 less 的第一個線程。可是,該線程必須再次鎖定互斥鎖,而後才能從 pthread_cond_wait() 返回。
獲取互斥鎖可確保該線程再次以獨佔方式訪問緩衝區的數據結構。該線程隨後必須檢查緩衝區中是否確實存在可用空間。若是空間可用,該線程會向下一個可用的空位置中進行寫入。
與此同時,使用者線程可能正在等待項出如今緩衝區中。這些線程正在等待條件變量 more。剛在緩衝區中存儲內容的生成方線程會調用 pthread_cond_signal() 以喚醒下一個正在等待的使用者。若是沒有正在等待的使用者,此調用將不起做用。
最後,生成方線程會解除鎖定互斥鎖,從而容許其餘線程處理緩衝區的數據結構。
void producer(buffer_t *b, char item)
{
pthread_mutex_lock(&b->mutex);
while (b->occupied >= BSIZE)
pthread_cond_wait(&b->less, &b->mutex);
assert(b->occupied < BSIZE);
b->buf[b->nextin++] = item;
b->nextin %= BSIZE;
b->occupied++;
/* now: either b->occupied < BSIZE and b->nextin is the index
of the next empty slot in the buffer, or
b->occupied == BSIZE and b->nextin is the index of the
next (occupied) slot that will be emptied by a consumer
(such as b->nextin == b->nextout) */
pthread_cond_signal(&b->more);
pthread_mutex_unlock(&b->mutex);
}
請注意 assert() 語句的用法。除非在編譯代碼時定義了 NDEBUG,不然 assert() 在其參數的計算結果爲真(非零)時將不執行任何操做。若是參數的計算結果爲假(零),則該程序會停止。在多線程程序中,此類斷言特別有用。若是斷言失敗,assert() 會當即指出運行時問題。assert() 還有另外一個做用,即提供有用的註釋。
以 /* now: either b->occupied ... 開頭的註釋最好以斷言形式表示,可是因爲語句過於複雜,沒法用布爾值表達式來表示,所以將用英語表示。
斷言和註釋都是不變量的示例。這些不變量是邏輯語句,在程序正常執行時不該將其聲明爲假,除非是線程正在修改不變量中提到的一些程序變量時的短暫修改過程當中。固然,只要有線程執行語句,斷言就應當爲真。
使用不變量是一種極爲有用的方法。即便沒有在程序文本中聲明不變量,在分析程序時也應將其視爲不變量。
每次線程執行包含註釋的代碼時,生成方代碼中表示爲註釋的不變量始終爲真。若是將此註釋移到緊挨 mutex_unlock() 的後面,則註釋不必定仍然爲真。若是將此註釋移到緊跟assert() 以後的位置,則註釋仍然爲真。
所以,不變量可用於表示一個始終爲真的屬性,除非一個生成方或一個使用者正在更改緩衝區的狀態。線程在互斥鎖的保護下處理緩衝區時,該線程可能會暫時聲明不變量爲假。可是,一旦線程結束對緩衝區的操做,不變量即會恢復爲真。
示例 4–13 給出了使用者的代碼。該邏輯流程與生成方的邏輯流程相對稱。
char consumer(buffer_t *b)
{
char item;
pthread_mutex_lock(&b->mutex);
while(b->occupied <= 0)
pthread_cond_wait(&b->more, &b->mutex);
assert(b->occupied > 0);
item = b->buf[b->nextout++];
b->nextout %= BSIZE;
b->occupied--;
/* now: either b->occupied > 0 and b->nextout is the index
of the next occupied slot in the buffer, or
b->occupied == 0 and b->nextout is the index of the next
(empty) slot that will be filled by a producer (such as
b->nextout == b->nextin) */
pthread_cond_signal(&b->less);
pthread_mutex_unlock(&b->mutex);
return(item);
}
在任何一個時間點上,線程是可結合的(joinable),或者是分離的(detached)。一個可結合的線程可以被其餘線程收回其資源和殺死;在被其餘線程回收以前,它的存儲器資源(如棧)是不釋放的。相反,一個分離的線程是不能被其餘線程回收或殺死的,它的存儲器資源在它終止時由系統自動釋放。
線程的分離狀態決定一個線程以什麼樣的方式來終止本身。在默認狀況下線程是非分離狀態的,這種狀況下,原有的線程等待建立的線程結束。只有當pthread_join()函數返回時,建立的線程纔算終止,才能釋放本身佔用的系統資源。而分離線程不是這樣子的,它沒有被其餘的線程所等待,本身運行結束了,線程也就終止了,立刻釋放系統資源。程序員應該根據本身的須要,選擇適當的分離狀態。因此若是咱們在建立線程時就知道不須要了解線程的終止狀態,則能夠pthread_attr_t結構中的detachstate線程屬性,讓線程以分離狀態啓動。
設置線程分離狀態的函數爲pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)。第二個參數可選爲PTHREAD_CREATE_DETACHED(分離線程)和 PTHREAD _CREATE_JOINABLE(非分離線程)。這裏要注意的一點是,若是設置一個線程爲分離線程,而這個線程運行又很是快,它極可能在pthread_create函數返回以前就終止了,它終止之後就可能將線程號和系統資源移交給其餘的線程使用,這樣調用pthread_create的線程就獲得了錯誤的線程號。要避免這種狀況能夠採起必定的同步措施,最簡單的方法之一是能夠在被建立的線程裏調用pthread_cond_timewait函數,讓這個線程等待一下子,留出足夠的時間讓函數pthread_create返回。設置一段等待時間,是在多線程編程裏經常使用的方法。可是注意不要使用諸如wait()之類的函數,它們是使整個進程睡眠,並不能解決線程同步的問題。
另一個可能經常使用的屬性是線程的優先級,它存放在結構sched_param中。用函數pthread_attr_getschedparam和函數pthread_attr_setschedparam進行存放,通常說來,咱們老是先取優先級,對取得的值修改後再存放回去。
線程等待——正確處理線程終止
#include <pthread.h>
void pthread_exit(void *retval);
void pthread_join(pthread_t th,void *thread_return);//掛起等待th結束,*thread_return=retval;
int pthread_detach(pthread_t th);
若是線程處於joinable狀態,則只能只能被建立他的線程等待終止。
在Linux平臺默認狀況下,雖然各個線程之間是相互獨立的,一個線程的終止不會去通知或影響其餘的線程。可是已經終止的線程的資源並不會隨着線程的終止而獲得釋放,咱們須要調用 pthread_join() 來得到另外一個線程的終止狀態而且釋放該線程所佔的資源。(說明:線程處於joinable狀態下)
調用該函數的線程將掛起,等待 th 所表示的線程的結束。 thread_return 是指向線程 th 返回值的指針。須要注意的是 th 所表示的線程必須是 joinable 的,即處於非 detached(遊離)狀態;而且只能夠有惟一的一個線程對 th 調用 pthread_join() 。若是 th 處於 detached 狀態,那麼對 th 的 pthread_join() 調用將返回錯誤。
若是不關心一個線程的結束狀態,那麼也能夠將一個線程設置爲 detached 狀態,從而讓操做系統在該線程結束時來回收它所佔的資源。將一個線程設置爲detached 狀態能夠經過兩種方式來實現。一種是調用 pthread_detach() 函數,能夠將線程 th 設置爲 detached 狀態。另外一種方法是在建立線程時就將它設置爲 detached 狀態,首先初始化一個線程屬性變量,而後將其設置爲 detached 狀態,最後將它做爲參數傳入線程建立函數 pthread_create(),這樣所建立出來的線程就直接處於 detached 狀態。
建立 detach 線程:
pthread_t tid;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, THREAD_FUNCTION, arg);
總之爲了在使用 pthread 時避免線程的資源在線程結束時不能獲得正確釋放,從而避免產生潛在的內存泄漏問題,在對待線程結束時,要確保該線程處於 detached 狀態,否着就須要調用 pthread_join() 函數來對其進行資源回收。
ftok()
#include <sys/types.h>
#include <sys/ipc.h>
函數原型:
key_t ftok( const char * pathname , int proj_id );
參數:
pathname 就時你指定的文件名(該文件必須是存在並且能夠訪問的),id是子序號,雖 然爲int,可是隻有8個比特被使用(0-255)。
返回值: 成功時候返回key_t 類型的key值,失敗返回-1
msgget
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函數原型: int msgget ( key_t key , int msgflg );
函數描述:創建消息隊列
參數:
msgget()函數的第一個參數是消息隊列對象的關鍵字(key),函數將它與已有的消息隊
列對象的關鍵字進行比較來判斷消息隊列對象是否已經建立。而函數進行的具體操做是 由第二個參數,msgflg 控制的。它能夠取下面的幾個值:
IPC_CREAT :若是消息隊列對象不存在,則建立之,不然則進行打開操做;
IPC_EXCL:和IPC_CREAT 一塊兒使用(用」|」鏈接),若是消息對象不存在則建立之,否 則產生一個錯誤並返回。
返回值:
成功時返回隊列ID,失敗返回-1,錯誤緣由存於error中
EEXIST (Queue exists, cannot create)
EIDRM (Queue is marked for deletion)
ENOENT (Queue does not exist)
ENOMEM (Not enough memory to create queue)
ENOSPC (Maximum queue limit exceeded)
msgsnd函數:將消息送入消息隊列
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h
函數原型:int msgsnd ( int msgid , struct msgbuf*msgp , int msgsz, int msgflg );
參數說明:
傳給msgsnd()函數的第一個參數msqid 是消息隊列對象的標識符(由msgget()函數得
到),第二個參數msgp 指向要發送的消息所在的內存,第三個參數msgsz 是要發送信息 的長度(字節數),能夠用如下的公式計算:
msgsz = sizeof(struct mymsgbuf) - sizeof(long);
第四個參數是控制函數行爲的標誌,能夠取如下的值:
0,忽略標誌位;
IPC_NOWAIT,若是消息隊列已滿,消息將不被寫入隊列,控制權返回調用函數的線
程。若是不指定這個參數,線程將被阻塞直到消息被能夠被寫入。
smgbuf結構體定義以下:
struct smgbuf
{
long mtype;
char mtext [x] ; //長度由msgsz決定
}
msgflg 可設置爲 IPC_NOWAIT 。若是消息隊列已滿或其餘狀況沒法送入消息,則當即 返回 EAGIN
返回: 0 on success
-1 on error: errno = EAGAIN (queue is full, and IPC_NOWAIT was asserted)
EACCES (permission denied, no write permission)
EFAULT (msgp address isn't accessable – invalid)
EIDRM (The message queue has been removed)
EINTR (Received a signal while waiting to write)
EINVAL (Invalid message queue identifier, nonpositive
message type, or invalid message size)
ENOMEM (Not enough memory to copy message buffer)
msgrcv函數:從消息隊列中讀取消息
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
函數定義:int msgrcv( int msgid , struct msgbuf* msgp , int msgsz , long msgtyp, int msgflg);
參數:
函數的前三個參數和msgsnd()函數中對應的參數的含義是相同的。第四個參數mtype
指定了函數從隊列中所取的消息的類型。函數將從隊列中搜索類型與之匹配的消息並將 之返回。不過這裏有一個例外。若是mtype 的值是零的話,函數將不作類型檢查而自動返 回隊列中的最舊的消息。第五個參數依然是是控制函數行爲的標誌,取值能夠是:
0,表示忽略;
IPC_NOWAIT,若是消息隊列爲空,則返回一個ENOMSG,並將控制權交回調用函數
的進程。若是不指定這個參數,那麼進程將被阻塞直到函數能夠從隊列中獲得符合條件 的消息爲止。若是一個client 正在等待消息的時候隊列被刪除,EIDRM 就會被返回。若是 進程在阻塞等待過程當中收到了系統的中斷信號,EINTR 就會被返回。
MSG_NOERROR,若是函數取得的消息長度大於msgsz,將只返回msgsz 長度的信息,
剩下的部分被丟棄了。若是不指定這個參數,E2BIG 將被返回,而消息則留在隊列中不 被取出。
當消息從隊列內取出後,相應的消息就從隊列中刪除了。
msgbuf:結構體,定義以下:
struct msgbuf
{
long mtype ; //信息種類
char mtest[x];//信息內容 ,長度由msgsz指定
}
msgtyp: 信息類型。 取值以下:
msgtyp = 0 ,不分類型,直接返回消息隊列中的第一項
msgtyp > 0 ,返回第一項 msgtyp與 msgbuf結構體中的mtype相同的信息
msgtyp <0 , 返回第一項 mtype小於等於msgtyp絕對值的信息
msgflg:取值以下:
IPC_NOWAIT ,不阻塞
IPC_NOERROR ,若信息長度超過參數msgsz,則截斷信息而不報錯。
返回值:
成功時返回所獲取信息的長度,失敗返回-1,錯誤信息存於error
Number of bytes copied into message buffer
-1 on error: errno = E2BIG (Message length is greater than
msgsz,no MSG_NOERROR)
EACCES (No read permission)
EFAULT (Address pointed to by msgp is invalid)
EIDRM (Queue was removed during retrieval)
EINTR (Interrupted by arriving signal)
EINVAL (msgqid invalid, or msgsz less than 0)
ENOMSG (IPC_NOWAIT asserted, and no message exists in the queue to satisfy the request)
例子:
server.c
client.c
1.Linux「線程」
進程與線程之間是有區別的,不過Linux內核只提供了輕量進程的支持,未實現線程模型。Linux是一種「多進程單線程」的操做系統。Linux自己只有進程的概念,而其所謂的「線程」本質上在內核裏仍然是進程。
你們知道,進程是資源分配的單位,同一進程中的多個線程共享該進程的資源(如做爲共享內存的全局變量)。Linux中所謂的「線程」只是在被建立時clone了父進程的資源,所以clone出來的進程表現爲「線程」,這一點必定要弄清楚。所以,Linux「線程」這個概念只有在打冒號的狀況下才是最準確的。
目前Linux中最流行的線程機制爲LinuxThreads,所採用的就是線程-進程「一對一」模型,調度交給核心,而在用戶級實現一個包括信號處理在內的線程管理機制。LinuxThreads由Xavier Leroy (Xavier.Leroy@inria.fr)負責開發完成,並已綁定在GLIBC中發行,它實現了一種BiCapitalized面向Linux的Posix 1003.1c 「pthread」標準接口。Linuxthread能夠支持Intel、Alpha、MIPS等平臺上的多處理器系統。
按照POSIX 1003.1c 標準編寫的程序與Linuxthread 庫相連接便可支持Linux平臺上的多線程,在程序中需包含頭文件pthread. h,在編譯連接時使用命令:
gcc -D -REENTRANT -lpthread xxx. c |
2.「線程」控制
線程建立
進程被建立時,系統會爲其建立一個主線程,而要在進程中建立新的線程,則能夠調用pthread_create:
pthread_create(pthread_t *thread, const pthread_attr_t *attr, void * (start_routine)(void*), void *arg); |
pthread_self (void) ; |
pthread_exit (void * retval) ; |
pthread_cance (pthread_t thread) ; |
pthread_join (pthread_t thread, void** threadreturn); |
3.線程通訊
線程互斥
互斥意味着「排它」,即兩個線程不能同時進入被互斥保護的代碼。Linux下能夠經過pthread_mutex_t 定義互斥體機制完成多線程的互斥操做,該機制的做用是對某個須要互斥的部分,在進入時先獲得互斥體,若是沒有獲得互斥體,代表互斥部分被其它線程擁有,此時欲獲取互斥體的線程阻塞,直到擁有該互斥體的線程完成互斥部分的操做爲止。
下面的代碼實現了對共享全局變量x 用互斥體mutex 進行保護的目的:
int x; // 進程中的全局變量 pthread_mutex_t mutex; pthread_mutex_init(&mutex, NULL); //按缺省的屬性初始化互斥體變量mutex pthread_mutex_lock(&mutex); // 給互斥體變量加鎖 … //對變量x 的操做 phtread_mutex_unlock(&mutex); // 給互斥體變量解除鎖 |
pthread_cond_init (pthread_cond_t *cond, const pthread_condattr_t *attr); |
pthread_cond_wait (pthread_cond_t *cond, pthread_mutex_t *mutex); |
pthread_cond_broadcast (pthread_cond_t *cond) ; |
pthread_cond_signal (pthread_cond_t *cond) ; |
sem_init(sem_t *sem, int pshared, unsigned int val); |
sem_wait(sem_t *sem); |
sem_post(sem_t *sem); |
#include <stdio.h> #include <pthread.h> #define BUFFER_SIZE 16 // 緩衝區數量 struct prodcons { // 緩衝區相關數據結構 int buffer[BUFFER_SIZE]; /* 實際數據存放的數組*/ pthread_mutex_t lock; /* 互斥體lock 用於對緩衝區的互斥操做 */ int readpos, writepos; /* 讀寫指針*/ pthread_cond_t notempty; /* 緩衝區非空的條件變量 */ pthread_cond_t notfull; /* 緩衝區未滿的條件變量 */ }; /* 初始化緩衝區結構 */ void init(struct prodcons *b) { pthread_mutex_init(&b->lock, NULL); pthread_cond_init(&b->notempty, NULL); pthread_cond_init(&b->notfull, NULL); b->readpos = 0; b->writepos = 0; } /* 將產品放入緩衝區,這裏是存入一個整數*/ void put(struct prodcons *b, int data) { pthread_mutex_lock(&b->lock); /* 等待緩衝區未滿*/ if ((b->writepos + 1) % BUFFER_SIZE == b->readpos) { pthread_cond_wait(&b->notfull, &b->lock); } /* 寫數據,並移動指針 */ b->buffer[b->writepos] = data; b->writepos++; if (b->writepos > = BUFFER_SIZE) b->writepos = 0; /* 設置緩衝區非空的條件變量*/ pthread_cond_signal(&b->notempty); pthread_mutex_unlock(&b->lock); } /* 從緩衝區中取出整數*/ int get(struct prodcons *b) { int data; pthread_mutex_lock(&b->lock); /* 等待緩衝區非空*/ if (b->writepos == b->readpos) { pthread_cond_wait(&b->notempty, &b->lock); } /* 讀數據,移動讀指針*/ data = b->buffer[b->readpos]; b->readpos++; if (b->readpos > = BUFFER_SIZE) b->readpos = 0; /* 設置緩衝區未滿的條件變量*/ pthread_cond_signal(&b->notfull); pthread_mutex_unlock(&b->lock); return data; } /* 測試:生產者線程將1 到10000 的整數送入緩衝區,消費者線 程從緩衝區中獲取整數,二者都打印信息*/ #define OVER ( - 1) struct prodcons buffer; void *producer(void *data) { int n; for (n = 0; n < 10000; n++) { printf("%d --->\n", n); put(&buffer, n); } put(&buffer, OVER); return NULL; } void *consumer(void *data) { int d; while (1) { d = get(&buffer); if (d == OVER) break; printf("--->%d \n", d); } return NULL; } int main(void) { pthread_t th_a, th_b; void *retval; init(&buffer); /* 建立生產者和消費者線程*/ pthread_create(&th_a, NULL, producer, 0); pthread_create(&th_b, NULL, consumer, 0); /* 等待兩個線程結束*/ pthread_join(th_a, &retval); pthread_join(th_b, &retval); return 0; } |
事項 | WIN32 | VxWorks | Linux |
線程建立 | CreateThread | taskSpawn | pthread_create |
線程終止 | 執行完成後退出;線程自身調用ExitThread函數即終止本身;被其餘線程調用函數TerminateThread函數 | 執行完成後退出;由線程自己調用exit退出;被其餘線程調用函數taskDelete終止 | 執行完成後退出;由線程自己調用pthread_exit 退出;被其餘線程調用函數pthread_cance終止 |
獲取線程ID | GetCurrentThreadId | taskIdSelf | pthread_self |
建立互斥 | CreateMutex | semMCreate | pthread_mutex_init |
獲取互斥 | WaitForSingleObject、WaitForMultipleObjects | semTake | pthread_mutex_lock |
釋放互斥 | ReleaseMutex | semGive | phtread_mutex_unlock |
建立信號量 | CreateSemaphore | semBCreate、semCCreate | sem_init |
等待信號量 | WaitForSingleObject | semTake | sem_wait |
釋放信號量 | ReleaseSemaphore | semGive | sem_post |
何時須要建立線程池呢?簡單的說,若是一個應用須要頻繁的建立和銷燬線程,而任務執行的時間又很是短,這樣線程建立和銷燬的帶來的開銷就不容忽視,這時也是線程池該出場的機會了。若是線程建立和銷燬時間相比任務執行時間能夠忽略不計,則沒有必要使用線程池了。
下面列出線程的一些重要的函數
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
void pthread_exit(void *retval);
int pthread_mutex_destroy(pthread_mutex_t *mutex);
int pthread_mutex_init(pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr);
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond,
const pthread_condattr_t *restrict attr);
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
有時咱們會須要大量線程來處理一些相互獨立的任務,爲了不頻繁的申請釋放線程所帶來的開銷,咱們可使用線程池。下面是一個C語言實現的簡單的線程池。
頭文件:
1: #ifndef THREAD_POOL_H__
2: #define THREAD_POOL_H__
3:
4: #include <pthread.h>
5:
6: /* 要執行的任務鏈表 */
7: typedef struct tpool_work {
8: void* (*routine)(void*); /* 任務函數 */
9: void *arg; /* 傳入任務函數的參數 */
10: struct tpool_work *next;
11: }tpool_work_t;
12:
13: typedef struct tpool {
14: int shutdown; /* 線程池是否銷燬 */
15: int max_thr_num; /* 最大線程數 */
16: pthread_t *thr_id; /* 線程ID數組 */
17: tpool_work_t *queue_head; /* 線程鏈表 */
18: pthread_mutex_t queue_lock;
19: pthread_cond_t queue_ready;
20: }tpool_t;
21:
22: /*
23: * @brief 建立線程池
24: * @param max_thr_num 最大線程數
25: * @return 0: 成功 其餘: 失敗
26: */
27: int
28: tpool_create(int max_thr_num);
29:
30: /*
31: * @brief 銷燬線程池
32: */
33: void
34: tpool_destroy();
35:
36: /*
37: * @brief 向線程池中添加任務
38: * @param routine 任務函數指針
39: * @param arg 任務函數參數
40: * @return 0: 成功 其餘:失敗
41: */
42: int
43: tpool_add_work(void*(*routine)(void*), void *arg);
44:
45: #endif
實現:
1: #include <unistd.h>
2: #include <stdlib.h>
3: #include <errno.h>
4: #include <string.h>
5: #include <stdio.h>
6:
7: #include "tpool.h"
8:
9: static tpool_t *tpool = NULL;
10:
11: /* 工做者線程函數, 從任務鏈表中取出任務並執行 */
12: static void*
13: thread_routine(void *arg)
14: {
15: tpool_work_t *work;
16:
17: while(1) {
18: /* 若是線程池沒有被銷燬且沒有任務要執行,則等待 */
19: pthread_mutex_lock(&tpool->queue_lock);
20: while(!tpool->queue_head && !tpool->shutdown) {
21: pthread_cond_wait(&tpool->queue_ready, &tpool->queue_lock);
22: }
23: if (tpool->shutdown) {
24: pthread_mutex_unlock(&tpool->queue_lock);
25: pthread_exit(NULL);
26: }
27: work = tpool->queue_head;
28: tpool->queue_head = tpool->queue_head->next;
29: pthread_mutex_unlock(&tpool->queue_lock);
30:
31: work->routine(work->arg);
32: free(work);
33: }
34:
35: return NULL;
36: }
37:
38: /*
39: * 建立線程池
40: */
41: int
42: tpool_create(int max_thr_num)
43: {
44: int i;
45:
46: tpool = calloc(1, sizeof(tpool_t));
47: if (!tpool) {
48: printf("%s: calloc failed\n", __FUNCTION__);
49: exit(1);
50: }
51:
52: /* 初始化 */
53: tpool->max_thr_num = max_thr_num;
54: tpool->shutdown = 0;
55: tpool->queue_head = NULL;
56: if (pthread_mutex_init(&tpool->queue_lock, NULL) !=0) {
57: printf("%s: pthread_mutex_init failed, errno:%d, error:%s\n",
58: __FUNCTION__, errno, strerror(errno));
59: exit(1);
60: }
61: if (pthread_cond_init(&tpool->queue_ready, NULL) !=0 ) {
62: printf("%s: pthread_cond_init failed, errno:%d, error:%s\n",
63: __FUNCTION__, errno, strerror(errno));
64: exit(1);
65: }
66:
67: /* 建立工做者線程 */
68: tpool->thr_id = calloc(max_thr_num, sizeof(pthread_t));
69: if (!tpool->thr_id) {
70: printf("%s: calloc failed\n", __FUNCTION__);
71: exit(1);
72: }
73: for (i = 0; i < max_thr_num; ++i) {
74: if (pthread_create(&tpool->thr_id[i], NULL, thread_routine, NULL) != 0){
75: printf("%s:pthread_create failed, errno:%d, error:%s\n", __FUNCTION__,
76: errno, strerror(errno));
77: exit(1);
78: }
79:
80: }
81:
82: return 0;
83: }
84:
85: /* 銷燬線程池 */
86: void
87: tpool_destroy()
88: {
89: int i;
90: tpool_work_t *member;
91:
92: if (tpool->shutdown) {
93: return;
94: }
95: tpool->shutdown = 1;
96:
97: /* 通知全部正在等待的線程 */
98: pthread_mutex_lock(&tpool->queue_lock);
99: pthread_cond_broadcast(&tpool->queue_ready);
100: pthread_mutex_unlock(&tpool->queue_lock);
101: for (i = 0; i < tpool->max_thr_num; ++i) {
102: pthread_join(tpool->thr_id[i], NULL);
103: }
104: free(tpool->thr_id);
105:
106: while(tpool->queue_head) {
107: member = tpool->queue_head;
108: tpool->queue_head = tpool->queue_head->next;
109: free(member);
110: }
111:
112: pthread_mutex_destroy(&tpool->queue_lock);
113: pthread_cond_destroy(&tpool->queue_ready);
114:
115: free(tpool);
116: }
117:
118: /* 向線程池添加任務 */
119: int
120: tpool_add_work(void*(*routine)(void*), void *arg)
121: {
122: tpool_work_t *work, *member;
123:
124: if (!routine){
125: printf("%s:Invalid argument\n", __FUNCTION__);
126: return -1;
127: }
128:
129: work = malloc(sizeof(tpool_work_t));
130: if (!work) {
131: printf("%s:malloc failed\n", __FUNCTION__);
132: return -1;
133: }
134: work->routine = routine;
135: work->arg = arg;
136: work->next = NULL;
137:
138: pthread_mutex_lock(&tpool->queue_lock);
139: member = tpool->queue_head;
140: if (!member) {
141: tpool->queue_head = work;
142: } else {
143: while(member->next) {
144: member = member->next;
145: }
146: member->next = work;
147: }
148: /* 通知工做者線程,有新任務添加 */
149: pthread_cond_signal(&tpool->queue_ready);
150: pthread_mutex_unlock(&tpool->queue_lock);
151:
152: return 0;
153: }
154:
155:
測試代碼:
1: #include <unistd.h>
2: #include <stdio.h>
3: #include <stdlib.h>
4: #include "tpool.h"
5:
6: void *func(void *arg)
7: {
8: printf("thread %d\n", (int)arg);
9: return NULL;
10: }
11:
12: int
13: main(int arg, char **argv)
14: {
15: if (tpool_create(5) != 0) {
16: printf("tpool_create failed\n");
17: exit(1);
18: }
19:
20: int i;
21: for (i = 0; i < 10; ++i) {
22: tpool_add_work(func, (void*)i);
23: }
24: sleep(2);
25: tpool_destroy();
26: return 0;
27: }
這個實現是在調用tpool_destroy以後,僅將當前正在執行的任務完成以後就會退出,咱們也能夠修改代碼使得線程池在執行完任務鏈表中全部任務後再退出。