线程并发编程之线程锁
實(shí)現(xiàn)并發(fā)的方式有多種,其中有進(jìn)程、線程、基于異步事件機(jī)制的編程等等。而針對多線程編程應(yīng)為同一進(jìn)程下的多個線程之間是共享進(jìn)程的用戶地址空間和 pc 等資源。所以會存在著數(shù)據(jù)競爭的情況,故而就會涉及到線程同步機(jī)制(鎖機(jī)制)或者依靠 cpu 提供的指令集原子原語實(shí)現(xiàn)的無鎖編程。而本節(jié)主要講述 Linux 系統(tǒng)間的鎖機(jī)制。??
?
1 互斥鎖
在線程實(shí)際運(yùn)行過程中,我們經(jīng)常需要多個線程保持同步。
這時可以用互斥鎖來完成任務(wù)?;コ怄i的使用過程中,主要有
pthread_mutex_init、pthread_mutex_destory、pthread_mutex_lock、pthread_mutex_unlock
這幾個函數(shù)以完成鎖的初始化,鎖的銷毀,上鎖和釋放鎖操作。
1.1 鎖的創(chuàng)建
鎖可以被動態(tài)或靜態(tài)創(chuàng)建,可以用宏P(guān)THREAD_MUTEX_INITIALIZER來靜態(tài)的初始化鎖,采用這種方式比較容易理解,互斥鎖是pthread_mutex_t的結(jié)構(gòu)體,而這個宏是一個結(jié)構(gòu)常量,如下可以完成靜態(tài)的初始化鎖:
pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;
另外鎖可以用pthread_mutex_init函數(shù)動態(tài)的創(chuàng)建,函數(shù)原型如下:
int pthread_mutex_init(pthread_mutex_t*mutex, const pthread_mutexattr_t * attr)
1.2 鎖的屬性
互斥鎖屬性可以由pthread_mutexattr_init(pthread_mutexattr_t *mattr)來初始化,然后可以調(diào)用其他的屬性設(shè)置方法來設(shè)置其屬性。
互斥鎖的范圍:可以指定是該進(jìn)程與其他進(jìn)程的同步還是同一進(jìn)程內(nèi)不同的線程之間的同步??梢栽O(shè)置為PTHREAD_PROCESS_SHARE和PTHREAD_PROCESS_PRIVATE。默認(rèn)是后者,表示進(jìn)程內(nèi)使用鎖??梢允褂?/p>
int pthread_mutexattr_setpshared(pthread_mutexattr_t*mattr, int pshared)
pthread_mutexattr_getpshared(pthread_mutexattr_t*mattr,int *pshared)
用來設(shè)置與獲取鎖的范圍;
互斥鎖的類型:有以下幾個取值空間:
PTHREAD_MUTEX_TIMED_NP,這是缺省值,也就是普通鎖。當(dāng)一個線程加鎖以后,其余請求鎖的線程將形成一個等待隊(duì)列,并在解鎖后按優(yōu)先級獲得鎖。這種鎖策略保證了資源分配的公平性。 PTHREAD_MUTEX_RECURSIVE_NP,嵌套鎖,允許同一個線程對同一個鎖成功獲得多次,并通過多次unlock解鎖。如果是不同線程請求,則在加鎖線程解鎖時重新競爭。 PTHREAD_MUTEX_ERRORCHECK_NP,檢錯鎖,如果同一個線程請求同一個鎖,則返回EDEADLK,否則與PTHREAD_MUTEX_TIMED_NP類型動作相同。這樣就保證當(dāng)不允許多次加鎖時不會出現(xiàn)最簡單情況下的死鎖。 PTHREAD_MUTEX_ADAPTIVE_NP,適應(yīng)鎖,動作最簡單的鎖類型,僅等待解鎖后重新競爭。
可以用 pthread_mutexattr_settype(pthread_mutexattr_t *attr , int type) pthread_mutexattr_gettype(pthread_mutexattr_t *attr , int *type)
獲取或設(shè)置鎖的類型。
1.3 鎖的釋放
調(diào)用pthread_mutex_destory之后,可以釋放鎖占用的資源,但這有一個前提上鎖當(dāng)前是沒有被鎖的狀態(tài)。
1.4 鎖操作
對鎖的操作主要包括加鎖 pthread_mutex_lock()、解鎖pthread_mutex_unlock()和測試加鎖pthread_mutex_trylock()三個。
int pthread_mutex_lock(pthread_mutex_t*mutex) int pthread_mutex_unlock(pthread_mutex_t *mutex) int pthread_mutex_trylock(pthread_mutex_t *mutex) pthread_mutex_trylock()語義與pthread_mutex_lock()類似,不同的是在鎖已經(jīng)被占據(jù)時返回EBUSY而不是掛起等待。
1.5 代碼講解:
代碼說明1:互斥鎖基本應(yīng)用
#include <stdio.h> #include <pthread.h> #include <unistd.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int count = 0;void* consume(void *arg) {while(1){pthread_mutex_lock(&mutex);printf("************************consume begin lock\n"); printf("************************consumed %d\n",count); count++;sleep(2);printf("************************consume over lock\n"); pthread_mutex_unlock(&mutex); printf("************************I'm out of pthread_mutex\n"); sleep(1);}return NULL; }void* produce( void * arg ) {while(1){pthread_mutex_lock(&mutex );printf("product begin lock\n");printf("produced %d\n", count);printf("product over lock\n");pthread_mutex_unlock(&mutex );printf("I'm out of pthread_mutex\n");sleep(1);}return NULL; }int main( void ) {pthread_t thread1,thread2;pthread_create(&thread1, NULL, &produce, NULL );pthread_create(&thread2, NULL, &consume, NULL );pthread_join(thread1,NULL);pthread_join(thread2,NULL);return 0; }?
結(jié)果說明:
[root@rocket lock-free]# g++ -g -o pthread_mutex_lockpthread_mutex_lock.cpp -lpthread
[root@rocket lock-free]#./pthread_mutex_lock
product begin lock
produced 0
product over lock
I'm out of pthread_mutex
************************consume beginlock
************************consumed 0
/*中間等待了2秒但是product線程沒有執(zhí)行!*/
************************consume overlock
************************I'm out ofpthread_mutex
product begin lock
produced 1
product over lock
I'm out of pthread_mutex
product begin lock
produced 1
product over lock
I'm out of pthread_mutex
************************consume beginlock
************************consumed 1
************************consume overlock
************************I'm out ofpthread_mutex
product begin lock
produced 2
product over lock
I'm out of pthread_mutex
************************consume beginlock
************************consumed 2
************************consume overlock
************************I'm out ofpthread_mutex
代碼說明2:pthread_mutext_trylock使用
#include <stdio.h> #include <pthread.h> #include <unistd.h>pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; int count = 0;void* consume(void *arg) {while(1){pthread_mutex_lock(&mutex);printf("************************consume begin lock\n"); printf("************************consumed %d\n",count); count++;sleep(2);printf("************************consume over lock\n"); pthread_mutex_unlock(&mutex); printf("************************I'm out of pthread_mutex\n"); sleep(1);}return NULL; }void* produce( void * arg ) {while(1){if(pthread_mutex_trylock(&mutex ) == 0){printf("product begin lock\n");printf("produced %d\n", count );printf("product over lock\n");pthread_mutex_unlock(&mutex);printf("I'm out of pthread_mutex\n");sleep(1);}else{printf("I have try!But i can`t lock the mutex!\n");sleep(1);}}return NULL; }int main( void ) {pthread_t thread1,thread2;pthread_create(&thread1, NULL, &produce, NULL );pthread_create(&thread2, NULL, &consume, NULL );pthread_join(thread1,NULL);pthread_join(thread2,NULL);return 0; }?
結(jié)果說明:
[root@rocket lock-free]# g++ -g -o pthread_mutex_trylock pthread_mutex_trylock.cpp -lpthread
[root@rocket lock-free]#./pthread_mutex_trylock
************************consume beginlock
************************consumed 0
/* trylock沒有成功馬上返回! */
I have try!But i can`t lock the mutex!
I have try!But i can`t lock the mutex!
************************consume overlock
************************I'm out ofpthread_mutex
product begin lock
produced 1
product over lock
I'm out of pthread_mutex
************************consume beginlock
************************consumed 1
I have try!But i can`t lock the mutex!
I have try!But i can`t lock the mutex!
************************consume overlock
************************I'm out ofpthread_mutex
product begin lock
produced 2
product over lock
I'm out of pthread_mutex
************************consume beginlock
************************consumed 2
I have try!But i can`t lock the mutex!
I have try!But i can`t lock the mutex!
************************consume overlock
************************I'm out ofpthread_mutex
2 讀寫鎖
讀寫鎖是因?yàn)橛?種狀態(tài),所以可以有更高的并行性。
2.1 特性
一次只有一個線程可以占有寫模式的讀寫鎖, 但是可以有多個線程同時占有讀模式的讀寫鎖,正是因?yàn)檫@個特性,當(dāng)讀寫鎖是寫加鎖狀態(tài)時,在這個鎖被解鎖之前, 所有試圖對這個鎖加鎖的線程都會被阻塞。
當(dāng)讀寫鎖在讀加鎖狀態(tài)時, 所有試圖以讀模式對它進(jìn)行加鎖的線程都可以得到訪問權(quán), 但是如果線程希望以寫模式對此鎖進(jìn)行加鎖, 它必須阻塞直到所有的線程釋放鎖。
通常,當(dāng)讀寫鎖處于讀模式鎖住狀態(tài)時,如果有另外線程試圖以寫模式加鎖,讀寫鎖通常會阻塞隨后的讀模式鎖請求, 這樣可以避免讀模式鎖長期占用, 而等待的寫模式鎖請求長期阻塞。
2.2 適用性
讀寫鎖適合于對數(shù)據(jù)結(jié)構(gòu)的讀次數(shù)比寫次數(shù)多得多的情況。因?yàn)?#xff0c;讀模式鎖定時可以共享, 以寫模式鎖住時意味著獨(dú)占, 所以讀寫鎖又叫共享-獨(dú)占鎖。
2.3 API初始化和銷毀
#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,出錯則返回錯誤編號
同互斥鎖一樣,在釋放讀寫鎖占用的內(nèi)存之前,需要先通過pthread_rwlock_destroy對讀寫鎖進(jìn)行清理工作, 釋放由init分配的資源。
2.4 讀和寫
#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);
這3個函數(shù)分別實(shí)現(xiàn)獲取讀鎖, 獲取寫鎖和釋放鎖的操作. 獲取鎖的兩個函數(shù)是阻塞操作
同樣,非阻塞的函數(shù)為:
#include?<pthread.h> int?pthread_rwlock_tryrdlock(pthread_rwlock_t?*rwlock); int?pthread_rwlock_trywrlock(pthread_rwlock_t?*rwlock);
非阻塞的獲取鎖操作, 如果可以獲取則返回0,否則返回錯誤的EBUSY
2.5 代碼講解
代碼說明1:讀寫鎖基本應(yīng)用
#include <errno.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <bits/pthreadtypes.h>static pthread_rwlock_t rwlock; //讀寫鎖對象int count = 0;void *thread_function_read(void *arg) {while(1){pthread_rwlock_rdlock(&rwlock);printf("************************%d, read count %d\n", pthread_self(), count);sleep(1);pthread_rwlock_unlock(&rwlock);usleep(100);}return NULL; }void *thread_function_write(void *arg) {while(1){pthread_rwlock_wrlock(&rwlock);count++;printf("************************%d, write count %d\n", pthread_self(), count);sleep(5);pthread_rwlock_unlock(&rwlock);usleep(100);}return NULL; }int main(int argc, char *argv[]) {pthread_t rpthread1, rpthread2, wpthread;pthread_rwlock_init(&rwlock,NULL);pthread_create(&rpthread1, NULL, thread_function_read, NULL);pthread_create(&rpthread2, NULL, thread_function_read, NULL);pthread_create(&wpthread, NULL, thread_function_write, NULL);pthread_join(rpthread1, NULL); pthread_join(rpthread2, NULL); pthread_join(wpthread, NULL); pthread_rwlock_destroy(&rwlock); exit(EXIT_SUCCESS); }結(jié)果說明:
[root@rocket lock-free]#./pthread_rwlock
/* 2個讀線程互相不阻塞 */
************************1442944768,read count 0?
************************1432454912,read count 0
/* 寫線程阻塞所有其它線程 */
************************1421965056,write count 1
************************1442944768,read count 1
************************1432454912,read count 1
************************1421965056,write count 2
************************1442944768,read count 2
************************1432454912,read count 2
************************1421965056,write count 3
************************1442944768,read count 3
************************1432454912,read count 3
************************1421965056,write count 4
有意思的是,加入去掉上面代碼中thread_function_read和thread_function_write中的usleep(100),則會出現(xiàn)以下結(jié)果
[root@rocket lock-free]#./pthread_rwlock
************************-1896831232,read count 0
************************-1907321088,read count 0
************************-1907321088,read count 0
************************-1896831232,read count 0
************************-1907321088,read count 0
************************-1896831232,read count 0
************************-1907321088,read count 0
發(fā)現(xiàn)搶不到寫鎖,按我原先的理解,因?yàn)閞eader線程先啟動,所以首先是reader搶到鎖,reader搶到鎖以后,writer阻塞在鎖請求上,當(dāng)reader釋放以后,應(yīng)該輪到writer才對啊,可是不是這樣的!當(dāng)reader釋放后再次請求鎖時,還是能拿到!writer基本搶不到鎖!
查手冊寫到,"The pthread_rwlock_rdlock() function applies a read lock tothe read-write lock referenced by rwlock. The calling thread acquires the readlock if a writer does not hold the lock and there are no writers blocked on thelock. It is unspecified whether the calling thread acquires the lock when awriter does not hold the lock and there are writers waiting for the lock" 意思就是說,沒有writer在等寫鎖的時辰,reader是可以拿到讀鎖的。然則沒有劃定,若是有writer在期待寫鎖,該若何?
還好,Linux有pthread_rwlockattr_setkind_np這個函數(shù)。
enum
{
?PTHREAD_RWLOCK_PREFER_READER_NP,
?PTHREAD_RWLOCK_PREFER_WRITER_NP,
?PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP,
?PTHREAD_RWLOCK_DEFAULT_NP =PTHREAD_RWLOCK_PREFER_READER_NP
};
可是直接pthread_rwlockattr_setkind_np(&attr,PTHREAD_RWLOCK_PREFER_WRITER_NP);
沒用!為啥呢?連man頁都沒有,所以我思疑這個函數(shù)沒實(shí)現(xiàn),因而就用debuginfo-install glibc? 裝glibc的調(diào)試符號,然后用gdb跟進(jìn)去,發(fā)現(xiàn)pthread_rwlockattr_setkind_np確切是有實(shí)現(xiàn)的,代碼很簡單,更改了attr的一個成員變量。那是為啥呢?
再谷歌,終究找到了pthread_rwlockattr_setkind_np的man page,末尾有一段notes,讓我年夜汗:
“Setting the value read-write lockkind to PTHREAD_RWLOCK_PREFER_WRITER_NP, results in the same behavior assetting the value to PTHREAD_RWLOCK_PREFER_READER_NP. As long as a readerthread holds the lock the thread holding a write lock will be starved. Settingthe kind value to PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP, allows thewriter to run. However, the writer may not be recursive as is implied by thename. “
意思就是說,
PTHREAD_RWLOCK_PREFER_WRITER_NP和PTHREAD_RWLOCK_PREFER_READER_NP是一樣滴!應(yīng)當(dāng)設(shè)置成PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP才對!可是PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP也是名存實(shí)亡滴,它才不會recursive 呢。
這樣就有了代碼說明2:讀寫鎖優(yōu)先級的使用
#include <errno.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <bits/pthreadtypes.h>static pthread_rwlock_t rwlock; //讀寫鎖對象int count = 0;void *thread_function_read(void *arg) {while(1){pthread_rwlock_rdlock(&rwlock);printf("************************%d, read count %d\n", pthread_self(), count);sleep(1);pthread_rwlock_unlock(&rwlock);//usleep(100);}return NULL; }void *thread_function_write(void *arg) {while(1){pthread_rwlock_wrlock(&rwlock);count++;printf("************************%d, write count %d\n", pthread_self(), count);sleep(1);pthread_rwlock_unlock(&rwlock);usleep(100);}return NULL; }int main(int argc, char *argv[]) {pthread_t rpthread1, rpthread2, wpthread;pthread_rwlockattr_t attr; pthread_rwlockattr_setkind_np(&attr,PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);pthread_rwlock_init(&rwlock, &attr);pthread_create(&rpthread1, NULL, thread_function_read, NULL);pthread_create(&rpthread2, NULL, thread_function_read, NULL);pthread_create(&wpthread, NULL, thread_function_write, NULL);pthread_join(rpthread1, NULL); pthread_join(rpthread2, NULL); pthread_join(wpthread, NULL); pthread_rwlock_destroy(&rwlock); exit(EXIT_SUCCESS); }?
運(yùn)行結(jié)果:
[root@rocket lock-free]#./pthread_rwlock_withpriority
************************1529054976,read count 0
************************1518565120,read count 0
************************1508075264,write count 1
************************1529054976,read count 1
************************1518565120,read count 1
************************1508075264,write count 2
************************1529054976,read count 2
************************1518565120,read count 2
************************1508075264,write count 3
這樣就不會導(dǎo)致writer餓死。
代碼說明3:pthread_rwlock_tryrdlock使用
#include <errno.h> #include <pthread.h> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <bits/pthreadtypes.h>static pthread_rwlock_t rwlock; //讀寫鎖對象int count = 0;void *thread_function_read(void *arg) {int print_count = 0;while(1){if (pthread_rwlock_tryrdlock(&rwlock) == 0){printf("************************%d, read count %d\n", pthread_self(), count);sleep(1);pthread_rwlock_unlock(&rwlock);usleep(100);}else{print_count++;if (print_count % 10 == 0){printf("I have try!But i can`t lock the rdlock!\n");print_count = 0;}usleep(100);}}return NULL; }void *thread_function_write(void *arg) {while(1){pthread_rwlock_wrlock(&rwlock);count++;printf("************************%d, write count %d\n", pthread_self(), count);sleep(5);pthread_rwlock_unlock(&rwlock);usleep(100);}return NULL; }int main(int argc, char *argv[]) {pthread_t rpthread1, rpthread2, wpthread;pthread_rwlock_init(&rwlock,NULL);pthread_create(&rpthread1, NULL, thread_function_read, NULL);pthread_create(&rpthread2, NULL, thread_function_read, NULL);pthread_create(&wpthread, NULL, thread_function_write, NULL);pthread_join(rpthread1, NULL); pthread_join(rpthread2, NULL); pthread_join(wpthread, NULL); pthread_rwlock_destroy(&rwlock); exit(EXIT_SUCCESS); }?
結(jié)果說明:
************************1819674368,read count 0
************************1809184512,read count 0
************************1798694656,write count 1
/* trylock沒有成功馬上返回! */
I have try!But i can`t lock therdlock!
I have try!But i can`t lock therdlock!
I have try!But i can`t lock therdlock!
I have try!But i can`t lock therdlock!
************************1819674368,read count 1
************************1809184512,read count 1
************************1798694656, writecount 2
I have try!But i can`t lock therdlock!
I have try!But i can`t lock therdlock!
I have try!But i can`t lock therdlock!
I have try!But i can`t lock therdlock!
3 自旋鎖
自旋鎖是SMP架構(gòu)中的一種low-level的同步機(jī)制。 當(dāng)線程A想要獲取一把自旋鎖而該鎖又被其它線程鎖持有時,線程A會在一個循環(huán)中自旋以檢測鎖是不是已經(jīng)可用了。對于自旋鎖需要注意:
由于自旋時不釋放CPU,因而持有自旋鎖的線程應(yīng)該盡快釋放自旋鎖,否則等待該自旋鎖的線程會一直在那里自旋,這就會浪費(fèi)CPU時間。
持有自旋鎖的線程在sleep之前應(yīng)該釋放自旋鎖以便其它線程可以獲得自旋鎖。(在內(nèi)核編程中,如果持有自旋鎖的代碼sleep了就可能導(dǎo)致整個系統(tǒng)掛起)
Pthreads提供的與Spin Lock鎖操作相關(guān)的API主要有:
intpthread_spin_destroy(pthread_spinlock_t *);
int pthread_spin_init(pthread_spinlock_t*, int);
intpthread_spin_lock(pthread_spinlock_t *);
intpthread_spin_trylock(pthread_spinlock_t *);
intpthread_spin_unlock(pthread_spinlock_t *);
3.1 初始化自旋鎖
pthread_spin_init用來申請使用自旋鎖所需要的資源并且將它初始化為非鎖定狀態(tài)。pshared的取值及其含義:
PTHREAD_PROCESS_SHARED:該自旋鎖可以在多個進(jìn)程中的線程之間共享。
PTHREAD_PROCESS_PRIVATE:僅初始化本自旋鎖的線程所在的進(jìn)程內(nèi)的線程才能夠使用該自旋鎖。
3.2 獲得一個自旋鎖
pthread_spin_lock用來獲取(鎖定)指定的自旋鎖. 如果該自旋鎖當(dāng)前沒有被其它線程所持有,則調(diào)用該函數(shù)的線程獲得該自旋鎖.否則該函數(shù)在獲得自旋鎖之前不會返回。如果調(diào)用該函數(shù)的線程在調(diào)用該函數(shù)時已經(jīng)持有了該自旋鎖,則結(jié)果是不確定的。
3.3 嘗試獲取一個自旋鎖
pthread_spin_trylock會嘗試獲取指定的自旋鎖,如果無法獲取則理解返回失敗。
3.4 釋放(解鎖)一個自旋鎖
pthread_spin_unlock用于釋放指定的自旋鎖。
3.5 銷毀一個自旋鎖
pthread_spin_destroy用來銷毀指定的自旋鎖并釋放所有相關(guān)聯(lián)的資源(所謂的所有指的是由pthread_spin_init自動申請的資源)在調(diào)用該函數(shù)之后如果沒有調(diào)用pthread_spin_init重新初始化自旋鎖,則任何嘗試使用該鎖的調(diào)用的結(jié)果都是未定義的。如果調(diào)用該函數(shù)時自旋鎖正在被使用或者自旋鎖未被初始化則結(jié)果是未定義的。
4 特性對比
| 互斥鎖mutex | 會導(dǎo)致線程切換 | 一般情況下的首選 |
| 讀寫鎖rwlock | 同一時間只能有一個writer 可以同時有多個reader | 讀多寫少的場景 |
| 自旋鎖spinlock | 不會導(dǎo)致線程切換 會導(dǎo)致CPU利用率升高 適合小代碼段 | 小代碼段,加鎖不是很頻繁的場景 |
總結(jié)
以上是生活随笔為你收集整理的线程并发编程之线程锁的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 【Python】Matplotlib利用
- 下一篇: Scrapy 爬虫框架四 —— 动态网页