<script type="text/javascript"></script>
1 Introduction
不用介紹了吧…
2 Thread Concepts
1. Thread由下面部分組成:
a. Thread ID
b. Stack
c. Policy
d. Signal mask
e. Errno
f. Thread-Specific Data
3 Thread Identification
1. pthread_t用於表示Thread ID,具體內容根據實現的不同而不同,有可能是一個Structure,因此不能將其看作爲整數
2. pthread_equal函數用於比較兩個pthread_t是否相等
#i nclude <pthread.h>
int pthread_equal(pthread_t tid1, pthread_t tid2)
|
3. pthread_self函數用於獲得本線程的thread id
#i nclude <pthread.h>
pthread _t pthread_self(void);
|
4 Thread Creation
1. 創建線程可以調用pthread_create函數:
#i nclude <pthread.h>
int pthread_create(
pthread_t *restrict tidp,
const pthread_attr_t *restrict attr,
void *(*start_rtn)(void *), void *restrict arg);
|
a. pthread_t *restrict tidp:返回最後創建出來的Thread的Thread ID
b. const pthread_attr_t *restrict attr:指定線程的Attributes,後面會講道,現在可以用NULL
c. void *(*start_rtn)(void *):指定線程函數指針,該函數返回一個void *,參數也爲void*
d. void *restrict arg:傳入給線程函數的參數
e. 返回錯誤值。
2. pthread函數在出錯的時候不會設置errno,而是直接返回錯誤值
4. 不能對創建的新線程和當前創建者線程的運行順序作出任何假設
5 Thread Termination
1. exit, _Exit, _exit用於中止當前進程,而非線程
2. 中止線程可以有三種方式:
a. 在線程函數中return
b. 被同一進程中的另外的線程Cancel掉
c. 線程調用pthread_exit函數
3. pthread_exit和pthread_join函數的用法:
a. 線程A調用pthread_join(B, &rval_ptr),被Block,進入Detached狀態(如果已經進入Detached狀態,則pthread_join函數返回EINVAL)。如果對B的結束代碼不感興趣,rval_ptr可以傳NULL。
b. 線程B調用pthread_exit(rval_ptr),退出線程B,結束代碼爲rval_ptr。注意rval_ptr指向的內存的生命週期,不應該指向B的Stack中的數據。
c. 線程A恢復運行,pthread_join函數調用結束,線程B的結束代碼被保存到rval_ptr參數中去。如果線程B被Cancel,那麼rval_ptr的值就是PTHREAD_CANCELLED。
兩個函數原型如下:
#i nclude <pthread.h>
void pthread_exit(void *rval_ptr);
int pthread_join(pthread_t thread, void **rval_ptr);
|
4. 一個Thread可以要求另外一個Thread被Cancel,通過調用pthread_cancel函數:
#i nclude <pthread.h>
void pthread_cancel(pthread_t tid)
|
該函數會使指定線程如同調用了pthread_exit(PTHREAD_CANCELLED)。不過,指定線程可以選擇忽略或者進行自己的處理,在後面會講到。此外,該函數不會導致Block,只是發送Cancel這個請求。
5. 線程可以安排在它退出的時候,某些函數自動被調用,類似atexit()函數。需要調用如下函數:
#i nclude <pthread.h>
void pthread_cleanup_push(void (*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);
|
這兩個函數維護一個函數指針的Stack,可以把函數指針和函數參數值push/pop。執行的順序則是從棧頂到棧底,也就是和push的順序相反。
在下面情況下pthread_cleanup_push所指定的thread cleanup handlers會被調用:
a. 調用pthread_exit
b. 相應cancel請求
c. 以非0參數調用pthread_cleanup_pop()。(如果以0調用pthread_cleanup_pop(),那麼handler不會被調用
有一個比較怪異的要求是,由於這兩個函數可能由宏的方式來實現,因此這兩個函數的調用必須得是在同一個Scope之中,並且配對,因爲在pthread_cleanup_push的實現中可能有一個{,而pthread_cleanup_pop可能有一個}。因此,一般情況下,這兩個函數是用於處理意外情況用的,舉例如下:
void *thread_func(void *arg)
{
pthread_cleanup_push(cleanup, “handler”)
// do something
Pthread_cleanup_pop(0);
return((void *)0);
}
|
6. 進程函數和線程函數的相關性:
Process Primitive
|
Thread Primitive
|
Description
|
fork
|
pthread_create
|
創建新的控制流
|
exit
|
pthread_exit
|
退出已有的控制流
|
waitpid
|
pthread_join
|
等待控制流並獲得結束代碼
|
atexit
|
pthread_cleanup_push
|
註冊在控制流退出時候被調用的函數
|
getpid
|
pthread_self
|
獲得控制流的id
|
abort
|
pthread_cancel
|
請求非正常退出
|
7. 缺省情況下,一個線程A的結束狀態被保存下來直到pthread_join爲該線程被調用過,也就是說即使線程A已經結束,只要沒有線程B調用pthread_join(A),A的退出狀態則一直被保存。而當線程處於Detached狀態之時,黨線程退出的時候,其資源可以立刻被回收,那麼這個退出狀態也丟失了。在這個狀態下,無法爲該線程調用pthread_join函數。我們可以通過調用pthread_detach函數來使指定線程進入Detach狀態:
#i nclude <pthread.h>
int pthread_detach(pthread_t tid);
|
通過修改調用pthread_create函數的attr參數,我們可以指定一個線程在創建之後立刻就進入Detached狀態
6 Thread Synchronization
1. 互斥量:Mutex
a. 用於互斥訪問
b. 類型:pthread_mutex_t,必須被初始化爲PTHREAD_MUTEX_INITIALIZER(用於靜態分配的mutex,等價於pthread_mutex_init(…, NULL))或者調用pthread_mutex_init。Mutex也應該用pthread_mutex_destroy來銷燬。這兩個函數原型如下:(attr的具體含義下一章討論)
#i nclude <pthread.h>
int pthread_mutex_init(
pthread_mutex_t *restrict mutex,
const pthread_mutexattr_t *restrict attr)
int pthread_mutex_destroy(pthread_mutex_t *mutex);
|
c. pthread_mutex_lock用於Lock Mutex,如果Mutex已經被Lock,該函數調用會Block直到Mutex被Unlock,然後該函數會Lock Mutex並返回。pthread_mutex_trylock類似,只是當Mutex被Lock的時候不會Block,而是返回一個錯誤值EBUSY。pthread_mutex_unlock則是unlock一個mutex。這三個函數原型如下:
#i nclude <pthread.h>
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
|
2. 讀寫鎖:Reader-Writer Locks
a. 多個線程可以同時獲得讀鎖(Reader-Writer lock in read mode),但是隻有一個線程能夠獲得寫鎖(Reader-writer lock in write mode)
b. 讀寫鎖有三種狀態
i. 一個或者多個線程獲得讀鎖,其他線程無法獲得寫鎖
ii. 一個線程獲得寫鎖,其他線程無法獲得讀鎖
iii. 沒有線程獲得此讀寫鎖
c. 類型爲pthread_rwlock_t
d. 創建和關閉方法如下:
#i nclude <pthread.h>
int pthread_rwlock_init(
pthread_rwlock_t *restrict rwlock,
const pthread_rwlockattr_t *restrict attr)
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
|
e. 獲得讀寫鎖的方法如下:
#i nclude <pthread.h>
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);
|
pthread_rwlock_rdlock:獲得讀鎖
pthread_rwlock_wrlock:獲得寫鎖
pthread_rwlock_unlock:釋放鎖,不管是讀鎖還是寫鎖都是調用此函數
注意具體實現可能對同時獲得讀鎖的線程個數有限制,所以在調用pthread_rwlock_rdlock的時候需要檢查錯誤值,而另外兩個pthread_rwlock_wrlock和pthread_rwlock_unlock則一般不用檢查,如果我們代碼寫的正確的話。
3. Conditional Variable:條件
a. 條件必須被Mutex保護起來
b. 類型爲:pthread_cond_t,必須被初始化爲PTHREAD_COND_INITIALIZER(用於靜態分配的條件,等價於pthread_cond_init(…, NULL))或者調用pthread_cond_init
#i nclude <pthread.h>
int pthread_cond_init(
pthread_cond_t *restrict cond,
const pthread_condxattr_t *restrict attr)
int pthread_cond_destroy(pthread_cond_t *cond);
|
c. pthread_cond_wait函數用於等待條件發生(=true)。pthread_cond_timedwait類似,只是當等待超時的時候返回一個錯誤值ETIMEDOUT。超時的時間用timespec結構指定。此外,兩個函數都需要傳入一個Mutex用於保護條件
#i nclude <pthread.h>
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);
|
d. timespec結構定義如下:
struct timespec {
time_t tv_sec; /* seconds */
long tv_nsec; /* nanoseconds */
};
|
注意timespec的時間是絕對時間而非相對時間,因此需要先調用gettimeofday函數獲得當前時間,再轉換成timespec結構,加上偏移量。
e. 有兩個函數用於通知線程條件被滿足(=true):
#i nclude <pthread.h>
int pthread_cond_signal(pthread_cond_t *cond);
int pthread_cond_broadcast(pthread_cond_t *cond);
|
兩者的區別是前者會喚醒單個線程,而後者會喚醒多個線程。