前面已經有介紹過線程的概念,因此咱們知道,當兩個線程同時讀寫一個內存區域的時候結果多是不肯定的.咱們假設寫操做須要兩個存儲器訪問週期,
而讀操做只須要一個訪問週期.在寫操做執行了一個訪問週期後讀操做開始執行,那麼獲得的結果可能並非咱們想要的.
在這個需求下,咱們就須要瞭解鎖以及同步的知識以更好的開發高性能的程序.多線程
數據類型:互斥變量是用pthread_mutex_t數據類型表示的,下面是初始化以及銷燬互斥變量的函數原型函數
#include<pthread.h> int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr); //參數attr設置爲NULL則爲默認屬性 int pthread_mutex_destroy(pthread_mutex_t *mutex); //兩個函數若成功則返回0,不然返回錯誤編號
互斥量操做函數原型:性能
#include<pthread.h> int pthread_mutex_lock(pthread_t *mutex) int pthread_mutex_trylock(pthread_t *mutex) int pthread_mutex_unlock(pthread_t *mutex) //全部函數若成功則返回0,不然返回錯誤編號
函數原型:線程
#include<time.h> #include<pthread.h> int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict tsptr); //成功返回0,失敗則返回錯誤編號.
在超時到來前mutex被解鎖,則函數行爲與lock一致.若是超時則返回ETIMEDOUT.rest
假如咱們須要保護的變量被修改的次數遠遠小於被讀取的次數,此時咱們再使用mutex就會對性能形成一些浪費.由於在大量的讀操做中並不會形成
亂序問題.在這種狀況下咱們就能夠利用讀寫鎖來減小加鎖形成的性能損失.code
讀寫鎖經過結構體pthread_rwlock_t表示,不一樣於mutex,讀寫鎖在使用以前必須進行初始化,在釋放底層內存前必須銷燬.接口
#include<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); //兩個函數成功則返回0,不然返回錯誤編號.
函數原型:進程
#include<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); //全部函數成功則返回0,不然返回錯誤編號.
pthread_rwlock_unlock:不論以哪一種形式加鎖均可以經過unlock解鎖.若是但從函數表現來看,應該是鎖內部維護了狀態以及計數器,若是是
讀鎖則將鎖數量減1,若是是寫鎖則切換鎖狀態(Go語言的實現方法,我並無查過C語言的實現源碼)內存
讀寫鎖原語:資源
#include<pthread.h> int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); //兩個函數成功則返回0,不然返回錯誤編號.
行爲等同於mutex的鎖原語.
當咱們的需求不只僅是鎖住某個臨界區,而且還須要判斷某些條件是否成立,這個時候條件變量是比mutex更好的選擇.
爲何要用條件變量:
咱們來假設這樣一個場景,四個線程讀取緩衝區,兩個線程寫入緩衝區.假如此時緩衝區爲空,而且寫入線程阻塞等待數據,這種狀況下四個讀取線程會作什麼呢?它們會不停的循環進行加鎖-判斷緩衝區內容-緩衝區爲空-解鎖.
這樣的頻繁加鎖解鎖的操做很大程度上浪費了CPU資源,因此此時咱們須要引入條件變量來幫助咱們解決這一問題.
初始化:
#include<pthread.h> int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr); int pthread_cond_destroy(pthread_cond_t *restrict cond); //成功則返回0,失敗則返回錯誤代碼
wait:
#include<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 tsptr); //成功則返回0,失敗則返回錯誤代碼
wait利用mutex以及條件變量對線程進行阻塞,調用者將鎖住的互斥量傳遞給函數,函數將線程放在等待條件的線程列表上並對互斥量解鎖.這樣線程就不會錯過任何條件變化.當函數返回時,該條件再次
被加鎖.
timedwait對wait增長了超時限制(tsptr參數).
喚醒:
#include<pthread.h> int pthread_cond_signal(pthread_cond_t *cond); int pthread_cond_broadcast(pthread_cond_t *cond); //成功則返回0,失敗則返回錯誤代碼
自旋鎖與mutex相似,都是經過阻塞的形式阻止得到已被加鎖的鎖.
mutex被阻塞時直接陷入睡眠等待信號(sleep-waiting),而自旋鎖則是忙等待(busy-waiting),也就是說自旋鎖的等待是佔用CPU的
自旋鎖初始化:
#include<pthread.h> int pthread_spin_init(pthread_spinlock_t *lock, int pshared); int pthread_spin_destroy(pthread_spinlock_t *lock); //兩個函數若成功則返回0,不然返回錯誤編號
函數原型:
#include<pthread.h> int pthread_spin_lock(pthread_spinlock_t *lock); int pthread_spin_trylock(pthread_spinlock_t *lock); int pthread_spin_unlock(pthread_spinlock_t *lock); //全部函數若是成功則返回0,失敗返回錯誤編號
須要注意的是,使用自旋鎖的範圍其實很窄,除非頻繁切換線程的開銷很是大或者咱們持有鎖的時間很是短,不然使用自旋鎖對cpu的佔用其實很高.
屏障是用戶協調多個線程並行工做的同步機制.屏障容許每一個線程等待,直到全部的線程都到達某一點(和go裏面的WaitGroup同樣).
初始化:
#include<pthread.h> int pthread_barrier_init(pthread_barrier_t *restrict barrier, const pthread_barrierattr_t *restrict attr,unsigned int count); int pthread_barrier_destroy(pthread_barrier_t *barrier); //兩個函數若成功則返回0,不然返回錯誤編號
函數原型:
#include<pthread.h> int pthread_barrier_wait(pthread_barrier_t *barrier); //成功則返回0或者PTHREAD_BARRIER_SEGIAL_THREAD,不然返回錯誤編號
調用wait的函數會在條件不知足時阻塞,直到count計數知足以後全部線程被喚醒.
須要注意的是,到達屏障計數後屏障能夠被重置,但此時的屏障計數沒有改變.因此咱們想要重用須要destroy而後再init初始化.