Linux 线程———详解
1、線程的概念 和 基礎(chǔ)知識(shí)
1.1 什么是線程
線程可看作輕量級(jí)進(jìn)程(light weight process),Linux的線程本質(zhì)仍然是進(jìn)程。Linux先有進(jìn)程后有線程,當(dāng)創(chuàng)建了一個(gè)進(jìn)程時(shí),系統(tǒng)給他分配一段4G的虛擬內(nèi)存,并在其內(nèi)生成進(jìn)程的PCB,當(dāng)他調(diào)用相關(guān)函數(shù)創(chuàng)建一個(gè)線程時(shí),會(huì)為新的線程生成一個(gè)PCB也存放在當(dāng)前的4G虛擬內(nèi)存中,而原來(lái)的進(jìn)程也淪為一個(gè)線程。
所以,進(jìn)程和線程的區(qū)別是:是否共享地址空間。 進(jìn)程總是獨(dú)享4G的虛擬內(nèi)存,而多個(gè)線程共享一段4G的空間。
線程是CPU調(diào)度的最小單位,也是CPU分配時(shí)間片的單位,所以,線程越多的應(yīng)用程序獲得CPU的概率也就越大,所以使用多線程能夠提高程序的執(zhí)行效率。而進(jìn)程可看作只有一個(gè)線程的進(jìn)程,所以單進(jìn)程應(yīng)用程序 與 多線程應(yīng)用程序爭(zhēng)奪CPU時(shí)并不占優(yōu)勢(shì)。
進(jìn)程是資源分配的最小單位。 一個(gè)進(jìn)程獨(dú)占4G的虛擬內(nèi)存,而同意進(jìn)程創(chuàng)建的多個(gè)線程共同使用同一片4G的空間。
1.2 進(jìn)程 和 線程的關(guān)系
類UNIX系統(tǒng)中,進(jìn)程和線程關(guān)系密切。
創(chuàng)建線程使用的底層系統(tǒng)調(diào)用和進(jìn)程一樣,都是clone(),只不過(guò)創(chuàng)建進(jìn)程時(shí)需要新找一片4G空間,并從原來(lái)的4G空間拷貝大部分?jǐn)?shù)據(jù),而創(chuàng)建線程則不需要額外開辟地址空間。一個(gè)進(jìn)程創(chuàng)建線程之后就蛻變?yōu)榫€程。
進(jìn)程和(進(jìn)程創(chuàng)建的)線程都有各自的PCB,但PCB中指向內(nèi)存資源的三級(jí)頁(yè)表(虛擬地址到物理地址的映射)是相同的。
1.3 線程共享的資源
① 文件描述符表
② 信號(hào)的處理方式
③ 當(dāng)前工作目錄
④ 用戶ID 和 組ID
⑤ 全局變量
⑥ 虛擬內(nèi)存地址空間:.text/.data/.bss/.heap/共享庫(kù)(其實(shí)就是共享0-3G的空間,除了??臻g 和 errno變量)
1.4 線程非共享資源
① 線程ID
② 處理器現(xiàn)場(chǎng)和棧指針(內(nèi)核??臻g)
③ 獨(dú)立的??臻g(用戶??臻g)
④ errno變量
⑤ 信號(hào)屏蔽字
⑥ 線程的調(diào)度優(yōu)先級(jí)
1.5 線程的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):
① 提高程序并發(fā)性
② 開銷小
③ 數(shù)據(jù)通信、共享數(shù)據(jù)方便(不同線程可以使用全局變量)
缺點(diǎn):
① 線程使用第三方庫(kù)函數(shù),不穩(wěn)定
② 代碼不好調(diào)試,沒(méi)法用gdb調(diào)試
③ 對(duì)信號(hào)的支持不好
2、線程控制原語(yǔ)
2.1 pthread_self
#inlcude<pthread.h> pthread_t pthread_self(void); pthread_t 是 unsigned long 類型,打印的時(shí)候要用 %lu此調(diào)用永遠(yuǎn)不會(huì)失敗,返回調(diào)用的線程ID。線程ID用于在進(jìn)程中區(qū)分不同線程。
2.2 pthread_create
#include<pthread.h> int pthread_create(pthread_t* thread, const pthread_attr_t* attr, void* (*start_routine)(void*), void* arg);該函數(shù)用于創(chuàng)建一個(gè)線程,thread是傳出參數(shù),傳出線程ID。第二個(gè)參數(shù)用于指定線程屬性,傳入NULL表示使用默認(rèn)屬性。
第三個(gè)參數(shù)是個(gè)函數(shù)指針,是線程的主控函數(shù),第四個(gè)參數(shù)是該函數(shù)的參數(shù)。第四個(gè)參數(shù)要強(qiáng)制轉(zhuǎn)換成泛型(void*)然后進(jìn)行值傳遞即可,不能傳遞地址。因?yàn)樽泳€程有自己的虛擬??臻g用于存放函數(shù)中的局部變量,如果去訪問(wèn)原來(lái)進(jìn)程的地址空間,地址中存儲(chǔ)的數(shù)據(jù)可能已經(jīng)變了。
pthread_create成功返回0,失敗直接返回錯(cuò)誤號(hào),而不是-1。
注意:創(chuàng)建線程之后,有可能創(chuàng)建它的進(jìn)程先退出了,那么;進(jìn)程的存儲(chǔ)空間將被回收,線程也就無(wú)法執(zhí)行了。
2.3 pthread_exit
#include<pthread.h> void pthread_exit(void* retval);調(diào)用該函數(shù)的線程會(huì)直接退出,retval表示線程的退出值,我們必須將該參數(shù)強(qiáng)轉(zhuǎn)為泛型void*。
pthread_exit()、return、exit()的區(qū)別:
- pthread_exit() 只表示退出當(dāng)前線程。
- return 表示退出當(dāng)前函數(shù),當(dāng)在main函數(shù)中return時(shí),表示退出當(dāng)前進(jìn)程;當(dāng)在線程中return時(shí),表示退出當(dāng)前線程;當(dāng)在線程調(diào)用的其他函數(shù)中return時(shí),只是退出那個(gè)函數(shù)。
- exit 無(wú)論在哪都表示退出當(dāng)前進(jìn)程。
編寫多線程程序,一定要謹(jǐn)慎使用return 和 exit,盡量使用pthread_exit退出線程。
2.4 pthread_join函數(shù)回收線程
線程中也存在僵尸線程,所以創(chuàng)建線程后必須用pthread_join回收,其定義如下:
#include<pthread.h> int pthread_join(pthread_t thread, void** retval);該函數(shù)用于回收線程。任意線程都可以調(diào)用該函數(shù)來(lái)回收其他線程,這點(diǎn)與進(jìn)程不同,子進(jìn)程只能由父進(jìn)程回收。
阻塞等待線程退出,獲取線程的退出狀態(tài),即,pthread_exit函數(shù)里的參數(shù),若線程根本沒(méi)有調(diào)用該函數(shù),那么線程默認(rèn)返回0退出。
thread是我們要等待退出的線程的線程ID,retval是傳出參數(shù),用于獲取線程的退出值,即,pthread_exit里的那個(gè)參數(shù)。
在使用該函數(shù)時(shí)要注意retval參數(shù):① 一定要用void**強(qiáng)制轉(zhuǎn)換為泛型指針 ② 該函數(shù)是將pthread_exit里的退出值 復(fù)制到 retval所指向的位置。③ 該參數(shù)可以置為NULL,表示不需要獲取線程退出值。
該函數(shù)成功返回0,失敗返回錯(cuò)誤號(hào),可能的錯(cuò)誤號(hào)如下:
2.5 pthread_detach實(shí)現(xiàn)線程分離
線程分離狀態(tài): 處于線程分離狀態(tài)的線程結(jié)束后,自己自動(dòng)被回收(自動(dòng)清理PCB),無(wú)需別的線程調(diào)用pthread_join來(lái)回收,他們也不應(yīng)該調(diào)用pthread_join。
若進(jìn)程有該機(jī)制,則不會(huì)產(chǎn)生僵尸進(jìn)程。因?yàn)檫M(jìn)程的資源都自動(dòng)回收干凈了,不存在殘留。
pthread_cdtach函數(shù)定義如下:
#include<pthread.h> int pthread_detach(pthread_t thread);thread參數(shù)是要設(shè)置分離狀態(tài)的線程ID。該函數(shù)成功返回0,失敗返回錯(cuò)誤號(hào)。
當(dāng)一個(gè)線程被設(shè)置為分離狀態(tài)后,就不能使用pthread_join回收他了,如果調(diào)用了pthread_join,則會(huì)返回錯(cuò)誤號(hào)。
2.6 pthread_cancel殺死線程
pthread_cancel函數(shù)用于殺死線程,其定義如下:
#inlcude<pthread.h> int pthread_cancel(pthread_t thread);thread是要?dú)⑺赖木€程ID。該函數(shù)成功返回0,失敗返回錯(cuò)誤號(hào)。
該函數(shù)要注意兩點(diǎn):
① 若一個(gè)線程被殺死,那么他的退出值是-1,使用pthread_join獲取到的退出值是-1。
② 當(dāng)程序中執(zhí)行到pthread_cancel函數(shù)時(shí),不會(huì)立即殺死相關(guān)線程,而是有一定延時(shí)。需要等到被殺死的線程自己運(yùn)行到某個(gè)檢查點(diǎn)(取消點(diǎn))時(shí),才真正執(zhí)行殺死線程的動(dòng)作。檢查點(diǎn)基本都是系統(tǒng)調(diào)用,如printf底層調(diào)用的write、sleep底層調(diào)用的pause等。使用man 7 pthreads查看所有的檢查點(diǎn)。
pthread_testcancel是一個(gè)庫(kù)函數(shù),他也是一個(gè)檢查點(diǎn),該函數(shù)調(diào)用總是成功的,我們可以調(diào)用該函數(shù)來(lái)人為設(shè)置一個(gè)檢查點(diǎn)。
接收到殺死請(qǐng)求的目標(biāo)線程可以決定是否允許被殺死,以及如何殺死,這分別由如下兩個(gè)函數(shù)完成:
#include<pthread.h> int pthread_setcancelstate(int state, int* oldstate); int pthread_setcanceltype(int type, int* oldtype);3、進(jìn)程 和 線程的控制原語(yǔ)對(duì)比
fork() pthread_create() exit() pthread_exit() wait() pthread_join() kill() pthread_cancel() getpid() pthread_self()4、線程屬性
4.1 pthread_attr_t結(jié)構(gòu)體
線程的所有屬性都封裝在pthread_attr_t結(jié)構(gòu)體里,下面是它的定義:
typedef struct {int detachstate; 線程的分離狀態(tài)int schedpolicy; 線程調(diào)度策略int schedparam; 線程調(diào)度參數(shù)int inheritsched; 線程的繼承性int scope; 線程的作用域size_t guardsize; 線程末尾的警戒緩沖區(qū)大小int stackaddr_set; 線程的棧設(shè)置void* stackaddr; 線程棧的位置size_t stacksize; 線程棧的大小 }pthread_attr_t;線程末尾的警戒緩沖區(qū): 指兩個(gè)線程各自??臻g之間的間隔區(qū)域,這個(gè)區(qū)域用于防止上面的線程發(fā)生棧溢出,從而影響到下面線程的棧空間。我們可以使用guardsize來(lái)人為更改警戒緩沖區(qū)的大小。
線程棧的大小: 默認(rèn)情況下,一個(gè)進(jìn)程里的所有線程會(huì)均分整個(gè)進(jìn)程的棧空間(8192Kb),我們可以人為設(shè)置它的大小。
4.2 設(shè)置線程屬性
4.2.1 線程屬性初始化
當(dāng)你要自己設(shè)置線程的屬性時(shí),就需要使用如下函數(shù):
#include<pthread.h> int pthread_attr_init(pthread_attr_t* attr); 初始化線程屬性 int pthread_attr_destroy(pthread_attr_t* attr); 銷毀線程屬性所占用的資源這兩個(gè)函數(shù)成對(duì)出現(xiàn),有初始化就必有銷毀。而且必須先初始化線程屬性再調(diào)用pthread_create創(chuàng)建線程。
這兩個(gè)函數(shù)成功返回0,失敗返回錯(cuò)誤號(hào)。
4.2.2 設(shè)置線程分離狀態(tài)
其實(shí)既可以設(shè)置分離狀態(tài),也可以獲取線程是否處于分離狀態(tài):
#include<pthread.h> int pthread_attr_setdetachstate(pthread_attr_t* attr, int detachstate); 設(shè)置 int pthread_attr_getdetachstate(const pthread_attr_t* attr, int* detachstate); 獲取參數(shù) attr 都是已初始化的線程屬性。
參數(shù) detachstate 的值只有兩種:
PTHREAD_CREATE_DETACHED 分離狀態(tài) PTHREAD_CREATE_JOINABLE 不分離狀態(tài)在設(shè)置函數(shù)中,該參數(shù)是一個(gè)傳入?yún)?shù);在獲取函數(shù)中,該參數(shù)是一個(gè)傳出參數(shù),用于獲取狀態(tài)。
想要設(shè)置分離狀態(tài),必須在調(diào)用pthread_create創(chuàng)建線程之前設(shè)置好。
兩個(gè)函數(shù)成功返回0,失敗返回錯(cuò)誤號(hào)。
如果設(shè)置一個(gè)線程為分離狀態(tài),而這個(gè)線程運(yùn)行又非???#xff0c;他可能在pthread_create函數(shù)返回線程ID前就終止并自行回收了,他終止后可能將線程ID和系統(tǒng)資源移交給其他線程使用,這種情況下,調(diào)用pthread_create就得到錯(cuò)誤的線程ID。使用線程同步措施可以避免這種情況,方法之一就是調(diào)用pthread_cond_timedwait函數(shù)(后面講)。單不能使用wait函數(shù),他是使整個(gè)進(jìn)程睡眠,并不能解決線程同步的問(wèn)題。
5、線程同步
線程同步用來(lái)使多個(gè)線程對(duì)共享資源進(jìn)行協(xié)調(diào)訪問(wèn),使共享資源能夠按照正常的邏輯被操作,而不是這個(gè)線程操作一會(huì)那個(gè)線程操作一會(huì),造成共享數(shù)據(jù)被混亂訪問(wèn),程序的執(zhí)行結(jié)果未知。
5.1 互斥鎖 mutex(互斥量)
每個(gè)線程在對(duì)資源操作前都嘗試先加鎖,成功加鎖才能操作,操作結(jié)束后解鎖。由于鎖只有一個(gè),當(dāng)一個(gè)線程持有鎖后,其余線程拿不到鎖,所以會(huì)阻塞等待。但Linux為我們提供的鎖是建議鎖,并不強(qiáng)制性線程使用鎖,所以,如果又來(lái)一個(gè)線程不去加鎖,而是直接訪問(wèn)全局變量,也能夠訪問(wèn),這樣又會(huì)產(chǎn)生數(shù)據(jù)混亂。
5.1.1 互斥鎖的相關(guān)函數(shù)
#include<pthread.h> int pthread_mutex_init(pthread_mutex_t* restrict mutex, const pthread_mutexattr_t* restrict attr); 動(dòng)態(tài)初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; 在全局作用域下,執(zhí)行該語(yǔ)句與執(zhí)行上面的函數(shù)等價(jià),這種初始化方式稱為靜態(tài)初始化 int pthread_mutex_destroy(pthread_mutex_t* 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); 這五個(gè)函數(shù)成功返回0,失敗返回錯(cuò)誤號(hào)。pthread_mutex_t 類型,本質(zhì)是一個(gè)結(jié)構(gòu)體類型。該類型的變量就是鎖,所以它一般聲明為全局變量,我們把它看成一個(gè)整數(shù),只有0,1兩種取值,分別表示上鎖 和 沒(méi)上鎖。
pthread_mutex_init 函數(shù)
該函數(shù)用于初始化一個(gè)鎖。聲明一個(gè)pthread_mutex_t鎖之后不能立即使用,必須傳他的地址到該函數(shù)里進(jìn)行初始化(初始完是解鎖狀態(tài))。第二個(gè)參數(shù)用于設(shè)置鎖的屬性,置為NULL使用默認(rèn)屬性。
pthread_mutex_destroy 函數(shù)
該函數(shù)用于銷毀一個(gè)鎖。銷毀后的鎖就變成了一個(gè)未初始化的鎖,所以可以重新調(diào)用pthread_mutex_init初始化并投入使用。
可以銷毀一個(gè)初始化但未鎖定的鎖,但銷毀一個(gè)已鎖定的鎖、或 線程正在嘗試鎖定的鎖,又或者另一個(gè)線程正在調(diào)用pthread_cond_timedwait或pthread_cond_wait的鎖,都會(huì)導(dǎo)致未定義行為。
pthread_mutex_lock 函數(shù)
該函數(shù)用于鎖定一個(gè)鎖。可理解為令mutex的值-1。若調(diào)用該函數(shù)時(shí),鎖的值已經(jīng)是0,那么該線程會(huì)被阻塞,直到占用鎖的線程解鎖。
pthread_mutex_trylock 函數(shù)
該函數(shù)也用于鎖定一個(gè)鎖。但是該函數(shù)的鎖定行為是非阻塞的,即,有其他線程鎖定該鎖時(shí),該函數(shù)立即返回,不會(huì)等待哪個(gè)線程解鎖。
pthread_mutex_unlock 函數(shù)
該函數(shù)用于解鎖。
在訪問(wèn)共享資源前加鎖,訪問(wèn)結(jié)束后 立即解鎖 。鎖的粒度越小越好。
5.1.2 死鎖
① 一個(gè)線程對(duì)同一個(gè)互斥量加鎖兩次,那第二次加鎖就會(huì)被阻塞,線程就卡在這里動(dòng)不了了
解決方法:加鎖后立即解鎖
① 線程1擁有了A鎖,請(qǐng)求獲得B鎖;而線程2獲得了B鎖,請(qǐng)求獲得A鎖
解決方法:其中一個(gè)線程在請(qǐng)求第二把鎖的時(shí)候調(diào)用非阻塞版本,如果請(qǐng)求失敗,那么放棄已經(jīng)獲取的鎖,即,當(dāng)不能獲取所有鎖時(shí),放棄已經(jīng)占有的鎖。
③ 震蕩
5.2 讀寫鎖
讀寫鎖也叫共享-獨(dú)占鎖,它用于對(duì)全局?jǐn)?shù)據(jù)進(jìn)行讀寫操作的情景。讀寫鎖有三個(gè)狀態(tài),讀模式下加鎖(讀鎖),寫模式下加鎖(寫鎖),和 不加鎖狀態(tài)。
- 當(dāng)鎖處于讀模式下加鎖狀態(tài)時(shí),有其他線程也嘗試以讀模式狀態(tài)加鎖時(shí),能夠枷鎖成功,其他線程以寫模式試圖加鎖時(shí)被阻塞,即, 讀共享 。
- 當(dāng)鎖處于寫模式下加鎖狀態(tài)時(shí),其他線程以任何模式嘗試加鎖都會(huì)被阻塞,即, 寫?yīng)氄?/strong> 。
- 當(dāng)鎖處于不加鎖狀態(tài)時(shí),既有嘗試以讀模式加鎖的線程,又有嘗試以寫模式加鎖的線程,那么寫模式的線程會(huì)加鎖成功,即, 寫鎖優(yōu)先級(jí)高 。
讀寫鎖的相關(guān)函數(shù)
#include<pthread.h> int pthread_rwlock_init(pthread_rwlock_t* restrict rwlock, const pthread_rwlockattr_t* restrict attr); pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITITLIZER; int pthread_rwlock_destroy(pthread_rwlock_t* rwlock);int pthread_rwlock_rdlock(pthread_rwlock_t* rwlock); int pthread_rwlock_tryrdlock(pthread_rwlock_t* rwlock);int pthread_rwlock_wrlock(pthread_rwlock_t* rwlock); int pthread_rwlock_trywrlock(pthread_rwlock_t* rwlock);int pthread_rwlock_unlock(pthread_rwlock_t* rwlock); 這7個(gè)函數(shù)成功返回0,失敗返回錯(cuò)誤號(hào)。這些函數(shù)的使用和互斥鎖幾乎相同,不再贅述。
讀寫鎖適用于對(duì)數(shù)據(jù)讀的次數(shù)遠(yuǎn)大于寫的次數(shù) 的場(chǎng)景。
5.3 條件變量
條件變量并不是鎖,但它能阻塞線程,直至接收到 “條件成立” 的信號(hào)后,被阻塞的線程才能繼續(xù)執(zhí)行。
5.3.1 條件變量的相關(guān)函數(shù)
#include<pthread.h> int pthread_cond_init(pthread_cond_t* restrict cond, const pthread_cond_attr* restrict attr); pthread_cond_t cond = PTHREAD_COND_INITIALIZER; int pthread_cond_destroy(pthread_cond_t* cond);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 abstime);int pthread_cond_signal(pthread_cond_t* cond); int pthread_cond_broadcast(pthread_cond_t* cond); 上述6個(gè)函數(shù)成功返回0,失敗返回錯(cuò)誤號(hào)。條件變量的詳細(xì)講解,參考這篇博客,我愿稱之為最強(qiáng)條件變量入門博客。
這里只說(shuō)一下pthread_cond_wait函數(shù),理解該函數(shù)極其重要。wait函數(shù)用于阻塞當(dāng)前線程,“等待條件成立”。說(shuō)是“等待條件成立”,但是線程調(diào)用了wait只會(huì)阻塞,根本沒(méi)法去檢查條件,所以,等待條件成立其實(shí)是等待“條件成立”的信號(hào),條件成立后會(huì)有人主動(dòng)告訴他條件成立了。
而wait函數(shù)也不止阻塞線程這一個(gè)動(dòng)作:線程被阻塞后會(huì)立即解鎖,這合起來(lái)是一個(gè)原子操作,不會(huì)被其他線程打斷。
線程執(zhí)行了wait后的動(dòng)作可理解為:即使線程拿到鎖了,但是沒(méi)有收到信號(hào),線程就得等待,等待就還必須解鎖,好讓別人拿到鎖。
當(dāng)收到“條件成立”的信號(hào)后,wait也不會(huì)立即解除阻塞,而是先重新上鎖,再解除阻塞,這也是一個(gè)原子操作,不會(huì)被其他線程打斷。
5.3.2 條件變量的生產(chǎn)者消費(fèi)者模型
消費(fèi)者: 調(diào)用wait或timedwait阻塞等待產(chǎn)品(“條件成立”的信號(hào))
生產(chǎn)者: 一旦生產(chǎn)出了產(chǎn)品,就調(diào)用signal或broadcast通知消費(fèi)者產(chǎn)品生產(chǎn)好了
其中還涉及到很多加鎖解鎖的細(xì)節(jié)。
6、進(jìn)程間同步
6.1 信號(hào)量
上面互斥鎖、讀寫鎖以及條件變量都只能用于線程間同步,而信號(hào)量既可以用于線程間同步,又可用于進(jìn)程間同步。
不難發(fā)現(xiàn),與線程 以及線程同步 有關(guān)的所有函數(shù),(若有返回值)成功都返回0,失敗返回錯(cuò)誤號(hào)。而下面即將介紹的信號(hào)量相關(guān)函數(shù)將回歸正常,即,失敗返回-1。
6.1.1 信號(hào)量的相關(guān)函數(shù)
#include<semaphore.h> int sem_init(sem_t* sem, int pshared, unsigned int value); int sem_destroy(sem_t* sem);下面四個(gè)函數(shù)用于對(duì)信號(hào)量進(jìn)行 -- 操作,信號(hào)量最小是0,若信號(hào)量 = 0,則調(diào)用wait會(huì)阻塞 int sem_wait(sem_t* sem); int sem_trywait(sem_t* sem); int sem_timedwait(sem_t* sem, const struct timespec* abs_timeout);下面四個(gè)函數(shù)用于對(duì)信號(hào)量進(jìn)行 ++ 操作 int sem_post(sem_t* sem); 上述6個(gè)函數(shù)成功返回0,失敗返回-1并設(shè)置 errno。 sem_init 函數(shù):
該函數(shù)用于初始化一個(gè)sem_t類型變量,即信號(hào)量。第一個(gè)參數(shù)sem是傳出參數(shù)。
第二個(gè)參數(shù)表示這個(gè)信號(hào)量是在進(jìn)程間共享還是線程間共享。若pthread的值為0,那么該信號(hào)量將在線程間共享。若pthread的值不為0,那么信號(hào)量在進(jìn)程間共享,并且信號(hào)量應(yīng)該位于共享內(nèi)存(如mmap建立的映射區(qū))中的某個(gè)區(qū)域
不能初始化一個(gè)已經(jīng)初始化的信號(hào)量。
sem_post 函數(shù):
使信號(hào)量增加(解鎖)。如果信號(hào)量的值因此大于0,那么阻塞在sem_wait調(diào)用中的另一個(gè)進(jìn)程或線程將被喚醒并繼續(xù)鎖定信號(hào)量。
6.1.2 信號(hào)量中的生產(chǎn)者消費(fèi)者模型
需使用兩個(gè)信號(hào)量,一個(gè)是空位數(shù),一個(gè)是產(chǎn)品數(shù)。
生產(chǎn)者: 生產(chǎn)時(shí)需要判斷空位數(shù)信號(hào)量是否為0(使用wait函數(shù)),不為0才能生產(chǎn),否則阻塞,成功生產(chǎn)后需要增加產(chǎn)品數(shù)信號(hào)量(使用post函數(shù))
消費(fèi)者: 消費(fèi)時(shí)需要判斷產(chǎn)品數(shù)信號(hào)量是否為0(使用wait函數(shù)),不為0才能消費(fèi),否則阻塞,成功消費(fèi)后需要增加空位數(shù)信號(hào)量(使用post函數(shù))。
6.2 互斥鎖
其實(shí)互斥鎖不僅能用于線程間同步,還能用于進(jìn)程間同步。這需要在pthread_mutex_init初始化之前修改其屬性為進(jìn)程間共享。
6.2.1 修改mutex屬性為進(jìn)程間共享
相關(guān)函數(shù):
pthread_mutexattr_t attr; 聲明 mutex 屬性結(jié)構(gòu)體 int pthread_mutexattr_init(pthread_mutexattr_t* attr); 初始化 mutex 屬性 int pthread_mutexattr_destroy(pthread_mutexattr_t* attr); 銷毀 mutex 屬性int pthread_mutexattr_setpshared(pthread_mutexattr_t* attr, int pshared); pshared 的取值如下: PTHREAD_PROCESS_PRIVATE 線程鎖,mutex 的默認(rèn)屬性 PTHREAD_PROCESS_SHARED 進(jìn)程鎖6.3 文件鎖
這是講解視頻鏈接,啥時(shí)候用到啥時(shí)候來(lái)學(xué)吧。
7、子進(jìn)程如何處理從父進(jìn)程繼承來(lái)的鎖??
當(dāng)父進(jìn)程調(diào)用fork創(chuàng)建子進(jìn)程后:
① 即使父進(jìn)程中有多個(gè)線程,子進(jìn)程也只會(huì)繼承調(diào)用fork的那個(gè)線程的代碼
② 子進(jìn)程的地址空間是父進(jìn)程地址空間的拷貝,所以會(huì)繼承父進(jìn)程中所有的互斥鎖、讀寫鎖 和 條件變量, 包括其狀態(tài) 。
③ 鎖的狀態(tài)雖然會(huì)被繼承,但子進(jìn)程沒(méi)有任何手段得知鎖的狀態(tài)到底是什么
④ 子進(jìn)程繼承到的鎖是一個(gè)新鎖,雖然有初始狀態(tài)了,但是 和原來(lái)的鎖沒(méi)有任何瓜葛 ,即使父進(jìn)程中,該鎖解鎖了,子進(jìn)程中該鎖不會(huì)有任何變化。
明確以上4點(diǎn)后,就能理解這個(gè)問(wèn)題了。子進(jìn)程使用從父進(jìn)程繼承而來(lái)的鎖很容易導(dǎo)致死鎖。 因?yàn)?#xff0c;從父進(jìn)程繼承而來(lái)的鎖有可能是已經(jīng)上鎖了的(與父進(jìn)程的鎖毫無(wú)瓜葛,不可能被解鎖的),如果子進(jìn)程再調(diào)用lock上鎖,那就是一個(gè)線程上兩次鎖,造成死鎖。
為了解決這種情況,pthread專門提供了一個(gè)函數(shù),pthread_atfork,確保fork調(diào)用后父進(jìn)程和子進(jìn)程都擁有一個(gè)清楚的鎖狀態(tài),函數(shù)定義如下:
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));該函數(shù)的詳解看這里,講得很不錯(cuò)。
8、線程和信號(hào)
Linux中的線程和信號(hào),適配的不是很好,一般用的少,要用的時(shí)候去看《Linux高性能服務(wù)器編程》的第285頁(yè)。
9、多線程編程的注意事項(xiàng)
① 在多線程中調(diào)用庫(kù)函數(shù)時(shí),一定要使用其可重入版本,否則會(huì)導(dǎo)致未定義的結(jié)果。(Linux的很多庫(kù)函數(shù)都是不可重入的,但大多都提供了可重入版本,可重入版本在原函數(shù)名尾部加上 _r )
總結(jié)
以上是生活随笔為你收集整理的Linux 线程———详解的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: linux卸载apache服务器,cen
- 下一篇: svnadmin hotcopy整库拷贝