通過這篇文字,您將能夠解答如下問題:
如何來標識一個線程?
如何創建一個新線程?
如何實現單個線程的退出?
如何使調用線程阻塞等待指定線程的退出,並獲得退出線程的返回碼?
如何通過一個線程讓另外一個線程退出?
如何實現線程退出時的清理動作?
Unix系統如何實現線程之間的同步?
什麽情況會發生線程死鎖,如何避免死鎖?
讀寫鎖的使用方法。
什麽是條件變量,它有什麽作用?
如何使用條件變量?
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
2.如何創建一個新線程?
使用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);}
3. 如何實現單個線程的退出?
如果一個線程調用了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已經退出它的棧已經被銷毀。
5.如何通過一個線程讓另外一個線程退出?
調用pthread_cancel函數將導致tid所指向的線程終止運行。但是,一個線程可以選擇忽略其它線程控制該線程何時退出。註意,該函數並不等待線程終止,它僅僅提出要求。
#includeint pthread_cancel(pthread_t tid);Returns: 0 if OK, error number on failure
6.如何實現線程退出時的清理動作?
線程可以建立多個清理處理程序,這些程序記錄在棧中,也就是說他們的執行順序與註冊順序想法。使用如下函數註冊清理函數:
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
7.Unix系統如何實現線程之間的同步?
使用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。
8.什麽情況會發生線程死鎖,如何避免死鎖?
如果一個線程對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來避免死鎖的發生。
9.讀寫鎖的使用方法。
讀寫鎖的初始化與銷毀:
#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
10.什麽是條件變量,它有什麽作用?
條件變量是線程可用的另外一種同步機制。條件變量給多個線程提供了一個會合的場所。條件變量與互斥量一起使用時,允許線程以無競爭的方式等待特定條件的發生。條件本身是由互斥量保護的。線程在改變狀態前必須首先鎖住互斥量,其它線程在獲得互斥量之前不會覺察到這種變化。
11.如何使用條件變量?
條件變量的類型為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 調用者把鎖住的mutex傳遞給pthread_cond_wait,函數把調用線程放到等待條件變量的線程列表上,然後對互斥量解鎖,這兩個操作是原子操作。這樣就關閉了條件檢查和線程進入休眠狀態等待條件改變這兩個操作之間的時間窗口。pthread_cond_wait返回時,mutex會再次被鎖住。
註意,調用成功返回後,線程需要重新計算條件變量,因為其它線程可能已經改變了條件。
有兩個函數用於通知線程一個條件已經被滿足。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喚醒線程,但由於我們仍占用著鎖,所以這些線程又會立即阻塞)。
Tags: 線程 如何 一個 pthread_t 死鎖 創建
文章來源: