"參考1 https://computing.llnl.gov/tutorials/pthreads/" "參考2 http://man7.org/linux/man pages/man7/pthreads.7.html" join 阻塞調用線程,直至指定pthread_t線程終止 在同一個線程中重 ...
參考1 https://computing.llnl.gov/tutorials/pthreads/
參考2 http://man7.org/linux/man-pages/man7/pthreads.7.html
join
int pthread_join(pthread_t, void**);
阻塞調用線程,直至指定pthread_t線程終止- 在同一個線程中重覆調用join會導致錯誤
- 在創建線程的時候可以指定要創建的線程是否joinable,如果是,則可以join,否則(即detached)不可以。一般預設都是joinable
- POSIX指出線程should指定為joinable
- 如果確定一個線程需要join,那麼最好明確指定該線程joinable,通過如下四步:
- Declare a pthread attribute variable of the
pthread_attr_t data
type - Initialize the attribute variable with
pthread_attr_init()
- Set the attribute detached status with
pthread_attr_setdetachstate()
- When done, free library resources used by the attribute with
pthread_attr_destroy()
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
for(t=0; t<NUM_THREADS; t++) {
printf("Main: creating thread %ld\n", t);
rc = pthread_create(&thread[t], &attr, BusyWork, (void *)t); //一個attr可以給多個線程使用
if (rc) {
printf("ERROR; return code from pthread_create() is %d\n", rc);
exit(-1);
}
}
pthread_attr_destroy(&attr); //記得釋放資源。create執行完之後就可以釋放,而不用等待線程結束
- 如果確定一個線程不需要joinable,那麼應該明確考慮設置屬性為detached
通過
pthread_detach()
來設置線程為不可join,即使它被創建的時候被設置為joinable。這個動作不可逆stack
- POSIX沒有規定創建的線程的stack大小是多少,這是由implementation決定的
pthread_attr_setstacksize
可以用來設置需要的stack大小pthread_attr_getstackaddr
和pthread_attr_setstackaddr
可以用來設置stack需要放置到特定的記憶體區域
size_t stacksize;
pthread_attr_init(&attr);
pthread_attr_getstacksize (&attr, &stacksize);
printf("Default stack size = %li\n", stacksize);
size = 10000; //設置為10000bytes
pthread_attr_setstacksize (&attr, stacksize);
printf("set stack size = %li\n", stacksize);
pthread_create(&threads[t], &attr, dowork, (void *)t);
mutex
Creating and Destroying Mutexes
//destroy,成功則返回0
int pthread_mutex_destroy(pthread_mutex_t *mutex);
//動態初始化,成功則返回0. 如果attr為NULL,那麼將使用預設屬性,相當於PTHREAD_MUTEX_INITIALIZER
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
//使用預設參數靜態初始化
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//mutex屬性
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
- 被destroy的mutex可以使用
pthread_mutex_init
重新初始化 - destroy一個處於lock狀態的mutex,將會導致undefined行為
- 只有mutex可以用來執行synchronization,用它的copies來執行lock,unlock和trylock將導致undefined
不可以重覆初始化已經初始化了的mutex
Locking and Unlocking Mutexes
//如果別的線程已經lock,那會一直阻塞當前線程直至獲得鎖
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_trylock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
mutex類型 | 性質 |
---|---|
PTHREAD_MUTEX_NORMAL | 對mutex的重覆lock,即本線程已經lock了mutex,在沒有unlock之前又嘗試lock,將導致死鎖行為;unlock一個沒有被本線程lock或者沒有被任何線程lock的mutex,將導致未定義行為 |
PTHREAD_MUTEX_ERRORCHECK | 嘗試重覆lock一個mutex將不會死鎖,而是返回一個錯誤值;unlock一個沒有被本線程lock或者沒有被任何線程lock的mutex,也會返回錯誤值 |
PTHREAD_MUTEX_RECURSIVE | mutex可以被重覆lock。每次lock會增加相關計數,直至通過unlock使計數達到0時,才可以被別的線程lock;unlock一個沒有被本線程lock或者沒有被任何線程lock的mutex,也會返回錯誤值 |
PTHREAD_MUTEX_DEFAULT | 重覆lock會導致未定義行為(NORMAL中會導致死鎖);unlock一個沒有被本線程lock或者沒有被任何線程lock的mutex,也將導致未定義行為。 不過,在NDK的定義中,直接把PTHREAD_MUTEX_DEFAULT = PTHREAD_MUTEX_NORMAL |
pthread_mutex_trylock
與pthread_mutex_lock
只有一點區別:如果當前mutex被任意線程lock,pthread_mutex_trylock
都將會立刻返回。如果mutex是PTHREAD_MUTEX_RECURSIVE的,且mutex已經被當前調用線程lock,pthread_mutex_trylock
也同樣會導致計數增一,並返回success。- 如果正在等待lock的線程收到了一個signal,當其從signal handler返回之後,會繼續等待lock,就和signal沒有發生一樣
- 除非使用了 thread priority scheduling,否則多個正在等待lock的線程獲得lock的情況可能多少有點random
- 如果成功,這三個函數都是返回0,否則返回相應的error
Condition Variables
- mutex通過控制對數據的訪問許可權來達到同步;而condition variables則基於數據的值來控制同步
- 如果不使用condition variable,線程想要檢查某個條件則只能通過輪詢的方式,這將非常resource consuming,因為這期間線程將一直active。而使用condition variable則將在不使用輪詢的情況下實現此目標
condition variable 經常和mutex一起使用
Creating and Destroying Condition Variables
int pthread_cond_destroy(pthread_cond_t *cond);
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
int pthread_condattr_destroy(pthread_condattr_t *attr);
int pthread_condattr_init(pthread_condattr_t *attr);
- destory正由某個線程用於block的cv將導致未定義行為
- 只有cv自己能夠用於同步,任何基於它的copies調用
pthread_cond_wait(), pthread_cond_timedwait(), pthread_cond_signal(), pthread_cond_broadcast(), pthread_cond_destroy()
都會產生未定義行為 - 初始化一個已經初始化的cv會導致未定義行為;已經destory的cv可以再次初始化;
Waiting and Signaling on Condition Variables
一般使用流程:
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex,
const struct timespec *restrict abstime);
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
- 這兩個函數將導致調用線程block on the condition variable, 並且需要傳入一個由調用線程lock了的mutex,否則導致未定義行為
- 如果在wait之前,沒有明確lock對應mutex,可能並不會導致block
- 這兩個函數會原子的unlock the mutex,並且導致調用線程block on the condition variable。這裡的原子的意味著:只要其他線程lock了這個mutex,那麼這個線程對
pthread_cond_broadcast()
或pthread_cond_signal()
的調用都會產生調用wait的線程已經blocked on the condition variable的效果。 - 只要這兩個函數返回,那麼調用線程就已經lock了這個mutex
- 虛假喚醒Spurious wakeups 可能會產生,而且這並不違反標準,所以,即使調用線程被喚醒,也不意味著對某個值做出某種保證,應該再次確認條件是否真的滿足了。同時,考慮到線程之間的競爭,
pthread_cond_timedwait
由於超時返回之後,條件也可能已經滿足。總之。任何時候wait返回,都需要重新評估條件是否滿足,這點非常重要 - 一旦線程waits on the condition variable,那麼這個cv就和相應的mutex綁定了,在wait返回之前,不能再使用另外的mutex來調用wait,這會導致未定義行為
- condition wait是一個cancellation point。未明白
- 假設一個由於wait調用而block線程由於被canceled而unblocked,這個不會consume任何condition signal。
pthread_cond_timedwait()
和pthread_cond_wait()
是equivalent的,除了:當signaled或者broadcasted超過指定時間,pthread_cond_timedwait()
就會返回返回error。同時,cv還可以支持 Clock Selection,選擇不同的Clock來measure指定的時間- 當cv wait期間,一個signal產生了,那麼cv可能會繼續wait就像沒有中斷一樣,或者這會形成一個spurious wakeup,返回0.
推薦使用while迴圈替代if語句來檢查當前條件是否真的滿足,有如下三點好處:
- 如果有多個線程都是在wait相同過的wake up signal,那麼當其他任意一個被waked up之後,他們都有可能更改條件值,而導致條件不滿足
- 線程可能會因為程式bug而收到一個signal
- Pthreads library被容許產生虛假喚醒,而且這並不違反標準
int pthread_cond_broadcast(pthread_cond_t *cond);
int pthread_cond_signal(pthread_cond_t *cond);
- 當多於一個線程在wait的時候,應該使用
pthread_cond_broadcast()
- 在signal之前,應該先lock對應mutex,然後在signal之後,應該unlock對應mutex。如果忘記了unlock,那麼相應的wait線程會繼續blocked,因為他們無法獲得lock
結束線程
有:
- pthread_cancel
- pthread_exit
pthread_kill
參考https://www.cnblogs.com/biyeymyhjob/archive/2012/10/11/2720377.html
---其他函數
pthread_self
pthread_t pthread_self(void);
返回調用線程的thread idpthread_equal
int pthread_equal(pthread_t t1, pthread_t t2);
比較兩個ID是否相等,如果相等則返回not-zero value,不相等則返回0。由於pthread_t結果opaque,所以不應該用==
來比較pthread_once
int pthread_once(pthread_once_t *once_control, void (*init_routine)(void));
:在進程中,任何首次調用這個函數的線程,在pthread_once_t once_control = PTHREAD_ONCE_INIT
的時候,會調用init_routine
程式。並且當此函數返回的時候,init_routine
已經執行完了(這裡沒有說init_routine
會阻塞調用線程,可能考慮的是,當線程A已經調用init_routine
,而另外一個線程B也調用了pthread_once
,那麼是否B也會等待A調用的init_routine
執行完畢?)。如果成功完成,則pthread_once
返回0。如果once_control
參數不是PTHREAD_ONCE_INIT
,那麼行為將是undefined。在LinuxThreads中:
在LinuxThreads中,實際"一次性函數"的執行狀態有三種:NEVER(0)、IN_PROGRESS(1)、DONE (2),如果once初值設為1,則由於所有pthread_once()都必須等待其中一個激發"已執行一次"信號,因此所有pthread_once ()都會陷入永久的等待中;如果設為2,則表示該函數已執行過一次,從而所有pthread_once()都會立即返回0。
這個函數在當無法編輯進程的main函數,比如寫一個庫的時候,就很有用。
TODO:如果多個線程使用的init_routine
不相同怎麼辦?或者比如自己開發庫,但是user的main中已經使用不同的init_routine
調用了pthread_once
,那麼會是什麼結果?