概述
- 線程和進(jìn)程本質(zhì)上來說都屬于一個(gè)內(nèi)核調(diào)度單元,也就是說都可以作為一條單獨(dú)的執(zhí)行路徑。但是多進(jìn)程程序通常有一些限制,比如說創(chuàng)建一個(gè)進(jìn)程開銷很大并且進(jìn)程間通信比較困難;而線程就是為了打破這些限制而誕生的。由于多個(gè)線程同屬于一個(gè)進(jìn)程,所以多個(gè)線程之間共享數(shù)據(jù)段,堆段,共享內(nèi)存以及位于棧區(qū)的共享庫(kù)。這就意味著在多個(gè)線程之間共享數(shù)據(jù)十分方便。另外創(chuàng)建一個(gè)線程的開銷也很小。
- 除了全局內(nèi)存以外,線程間還共享以下內(nèi)容
- 進(jìn)程ID
- 進(jìn)程組ID和會(huì)話ID
- 控制終端
- 用戶ID和組ID
- 打開的文件描述符
- 信號(hào)處理程序
- 當(dāng)前工作目錄,文件權(quán)限掩碼
- 定時(shí)器
- 資源限制
- fcntl創(chuàng)建的記錄鎖
- 線程ID
- 信號(hào)掩碼
- 本地變量
- errno
- 線程特有數(shù)據(jù)
- 下表為Pthread API定義的一系列數(shù)據(jù)類型
| 數(shù)據(jù)類型 |
描述 |
| pthread_t |
線程ID |
| pthread_mutex_t |
互斥對(duì)象 |
| pthread_mutexattr_t |
互斥屬性對(duì)象 |
| pthread_cond_t |
條件變量 |
| pthread_condattr_t |
條件變量的屬性對(duì)象 |
| pthread_key_t |
線程特有屬性的鍵值 |
| pthread_once_t |
一次性初始化控制上下文 |
| pthread_attr_t |
線程的屬性對(duì)象 |
線程的創(chuàng)建和終止
- 在編譯使用了pthread API的程序時(shí)需要指定libpthread庫(kù),即增加編譯選項(xiàng)-lpthread
- 使用pthread_create函數(shù)創(chuàng)建一個(gè)新的線程,原型為
int pthread_create(pthread_t* thread,const pthread_attr* attr,void* (*start)(void*),void* arg),thread是放置線程ID的緩沖區(qū);attr是創(chuàng)建線程使用的線程屬性,一般設(shè)置為NULL;start是一個(gè)函數(shù)指針,指向該線程的執(zhí)行函數(shù);arg是傳遞給該線程的參數(shù),可以在start函數(shù)中獲取,一般是全局變量或者堆變量
- 可以使用如下方式終止線程的運(yùn)行:1.使用return返回并指定返回值。2.調(diào)用pthread_exit。3.調(diào)用pthread_cancel。4.任意線程調(diào)用exit將終止同一個(gè)進(jìn)程的所有其他線程。
- pthread_exit的函數(shù)原型為
void pthread_exit(void* retval),retval返回值可以由其他線程調(diào)用pthread_join獲得;但是對(duì)于線程的返回值不應(yīng)該放在線程私有棧中,因?yàn)榫€程結(jié)束后該線程棧被釋放,從而數(shù)據(jù)就不正確了;
- 可以調(diào)用pthread_self查看自身的線程ID,原型為
pthread_t pthread_self(void);另外,在調(diào)用pthread_create創(chuàng)建新線程時(shí),如果創(chuàng)建成功會(huì)返回對(duì)應(yīng)的線程ID;對(duì)于兩個(gè)線程ID的比較不能使用==而要調(diào)用pthread_equal,其原型為int pthread_equal(pthread_t t1,pthread_t t2)
- 使用pthread_join可以連接一個(gè)線程,即等待特定線程終止,獲取其返回值。如果線程未被分離,則必須由另一線程調(diào)用此函數(shù)連接,否則會(huì)產(chǎn)生僵尸線程。另外,進(jìn)程中的任意線程都可以連接其他線程。其函數(shù)原型為
int pthread_join(pthread_t thread,void** retval),如果retval非空值將會(huì)保存待連接線程的退出值(包括return和pthread_exit)。如果連接成功返回0,否則返回錯(cuò)誤值(直接返回errno)
- 默認(rèn)情況下,線程是可連接的,也就是說在線程退出時(shí),其他線程可以通過pthread_join來獲得退出值。但是如果不關(guān)心線程的退出值,那么可以讓系統(tǒng)去負(fù)責(zé)終止線程的回收工作。在這種情況下,需要調(diào)用pthread_detach來使得線程處于分離狀態(tài)。函數(shù)原型為
int pthread_detach(pthread_t thread),如果成功調(diào)用函數(shù)返回0,否則返回錯(cuò)誤值。另外,如果一個(gè)線程已經(jīng)處于分離狀態(tài),那么其他線程就不能再使用連接函數(shù)去回收該線程
線程同步
- 為避免線程修改共享變量時(shí)出現(xiàn)問題,必須使用互斥量來確保同一時(shí)刻只有一個(gè)線程能夠訪問共享變量?;コ饬恐挥袃煞N狀態(tài),已鎖定和未鎖定狀態(tài)。一旦線程鎖定一個(gè)互斥量,那么該線程成為該互斥量的所有者,也只有互斥量所有者才能為其解鎖,并且只有當(dāng)互斥量處于未鎖定狀態(tài)時(shí)才能被線程占有
- 互斥量可以有兩種初始化方式,其一是通過靜態(tài)分配,即
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;其二是通過pthread_mutex_init方法動(dòng)態(tài)初始化
- 在初始化后,互斥量處于未鎖定狀態(tài)。函數(shù)pthread_mutex_lock可以鎖定一個(gè)互斥量,而函數(shù)pthread_mutex_unlock可以將互斥量解鎖。如果所指定的mutex已經(jīng)被鎖定(被某個(gè)線程占有),那么調(diào)用pthread_mutex_lock的線程將被阻塞,直到該mutex解除鎖定狀態(tài)。并且如果有多個(gè)線程同時(shí)等待同一個(gè)互斥量,那么系統(tǒng)會(huì)隨機(jī)選一個(gè)線程,而不是確定的。對(duì)于被自己鎖定過的互斥量進(jìn)行加鎖要根據(jù)互斥量的不同屬性(可重入性)來判別。對(duì)于一個(gè)未被鎖定或者被其他線程鎖定的互斥量調(diào)用unlock會(huì)出錯(cuò)。兩個(gè)函數(shù)的原型如下
#include <pthread.h>
int pthread_mutex_lock(pthread_mutex_t* mutex);
int pthread_mutex_unlock(pthread_mutex_t* mutex);
- 由于pthread_mutex_lock函數(shù)在某些時(shí)刻會(huì)阻塞調(diào)用線程,而我們又不想發(fā)生這樣的情況。這時(shí)候我們就需要pthread_mutex_trylock和pthread_mutex_timedlock函數(shù)。如果指定的mutex已經(jīng)被鎖定,對(duì)其執(zhí)行trylock會(huì)失敗并立即返回EBUSY錯(cuò)誤;而如果對(duì)其調(diào)用timedlock則可以指定一個(gè)阻塞時(shí)間,如果超過這個(gè)阻塞時(shí)間,那么就返回失敗
- 為避免死鎖的出現(xiàn),應(yīng)該遵循一個(gè)原則,當(dāng)多個(gè)線程訪問一組互斥量時(shí),應(yīng)當(dāng)以相同的順序進(jìn)行鎖定;或者可以避免使用lock而代替使用trylock
- 當(dāng)互斥量是在共享內(nèi)存區(qū)域中分配或者需要使用非默認(rèn)的互斥量屬性時(shí)需要使用動(dòng)態(tài)分配的方式;并且當(dāng)使用完畢以后,需要調(diào)用pthread_mutex_destroy函數(shù)將其銷毀。如果互斥量所在的內(nèi)存是動(dòng)態(tài)分配的,在調(diào)用destroy函數(shù)之前,還需要先調(diào)用free函數(shù)將內(nèi)存釋放。兩個(gè)函數(shù)的原型如下
#include <pthread.h>
int pthread_mutex_init(pthread_mutex_t* mutex,const pthread_mutexattr_t* attr);
int pthread_mutex_destroy(pthread_mutex_t* mutex);
- 對(duì)于pthread_mutexattr_t類型的設(shè)置如下代碼所示,與互斥量的創(chuàng)建類似
pthread_mutex_t mutex;
pthread_mutexattr_t attr;
pthread_mutexattr_init(&attr);
pthread_mutexattr_settype(&attr,PTHREAD_MUTEX_RECURSIVE);
pthread_mutex_init(&mutex,&attr);
pthread_mutex_destroy(&mutex);
- 條件變量總是結(jié)合互斥量一起使用,條件變量就共享變量的狀態(tài)改變而發(fā)出通知,而互斥量則提供對(duì)共享變量的互斥訪問。條件變量也分為靜態(tài)和動(dòng)態(tài)兩種分配方式。靜態(tài)分配方式為
pthread_cond_t cond = PTHREAD_COND_INITIALIZER
- 對(duì)條件變量的操作方式主要有兩類,分別是喚醒和等待。分別對(duì)應(yīng)pthread_cond_signal()和pthread_cond_wait()兩個(gè)函數(shù)。需要注意的是wait函數(shù)的工作方式,之前說過條件變量需要結(jié)合互斥量一起使用,所以wait函數(shù)的工作流程是先解鎖mutex(以便能夠讓其他線程鎖定該mutex,不至于造成死鎖),然后阻塞工作線程直到另外的線程喚醒它,最后在鎖定mutex。另外,pthread_cond_broadcast函數(shù)會(huì)喚醒所有在等待該條件變量的線程,函數(shù)原型如下
#include <pthread.h>
int pthread_cond_signal(pthread_cont_t* cond);
int pthread_cond_broadcast(pthread_cont_t* cond);
int pthread_cond_wait(pthread_cont_t* cond,pthread_mutex_t* mutex);
- 在測(cè)試條件變量的判斷條件時(shí),需要使用while循環(huán)而非if判斷。理由是可能不止一個(gè)線程在等待該條件變量,也就是說條件變量的滿足并非等同于程序所需要的判斷條件滿足,所以應(yīng)該循環(huán)判斷
- 可以通過pthread_cond_init對(duì)條件變量進(jìn)行動(dòng)態(tài)分配,與mutex的動(dòng)態(tài)分配類似,也會(huì)有一個(gè)配套的destroy函數(shù)
線程特有數(shù)據(jù)
- 線程安全函數(shù)是指能夠被多個(gè)線程同時(shí)安全調(diào)用的函數(shù),這和可重入函數(shù)有點(diǎn)類似;但是可重入函數(shù)是線程安全函數(shù)的一個(gè)子集,也就是說可重入函數(shù)一定是線程安全函數(shù),但線程安全函數(shù)不一定是可重入函數(shù)(因?yàn)榭芍厝牒瘮?shù)不僅可以工作于不同線程之間,也可以工作在不同進(jìn)程中間)
- 有時(shí)候多線程程序會(huì)有這樣的需求,即不管創(chuàng)建了多少線程,一些初始化函數(shù)只調(diào)用一次,這對(duì)于一些庫(kù)函數(shù)來說十分常見。這可以使用pthread_once函數(shù)來實(shí)現(xiàn),once_control是一個(gè)指向pthread_once_t類型變量的指針,該參數(shù)用來確保只調(diào)用一次由init指針?biāo)赶虻某跏蓟瘮?shù)。在使用之前,pthread_once_t變量應(yīng)該這樣初始化
pthread_once_t once = PTHREAD_ONCE_INIT,另外該函數(shù)原型為int pthread_once(pthread_once_t* once_control,void (*init)(void))
- 線程特有數(shù)據(jù)可以使得函數(shù)為每一個(gè)調(diào)用線程維護(hù)一份變量的副本(互相獨(dú)立)。首先需要調(diào)用
int pthread_key_create(pthread_key_t* key,void (*destructor)(void*))來創(chuàng)建一個(gè)key來區(qū)分不同的線程特有數(shù)據(jù),并且該初始化函數(shù)只需要調(diào)用一次,一般會(huì)使用pthread_once來完成。另外還可以傳入一個(gè)解構(gòu)函數(shù)指針destructor,用來釋放與每個(gè)線程特定的數(shù)據(jù)資源;在創(chuàng)建完key后,需要?jiǎng)?chuàng)建每個(gè)線程獨(dú)立的存儲(chǔ)數(shù)據(jù)的內(nèi)存塊(通過使用malloc等函數(shù)),每個(gè)線程調(diào)用函數(shù)時(shí)只會(huì)申請(qǐng)一次內(nèi)存;然后調(diào)用int pthread_setspecific(pthread_key_t key,const void* value)將之前申請(qǐng)的內(nèi)存空間與key相關(guān)聯(lián);在之后調(diào)用void* pthread_getspecific(pthread_key_t key)時(shí),會(huì)直接返回與key關(guān)聯(lián)的內(nèi)存塊。如果是首次調(diào)用,即還沒調(diào)用setspecific完成綁定,那么getspecific會(huì)返回NULL。示例代碼如下(假設(shè)func是會(huì)被多個(gè)線程調(diào)用的函數(shù))
#include <stdio.h>
#include <pthread.h>
#define BUFFSIZE 4096
static pthread_once_t once = PTHREAD_ONCE_INIT;
static pthread_key_t key;
void finalize(void* buf)
{
free(buf);
}
void createKey(void)
{
int res = pthread_key_create(&key,finalize);
if(res != 0)
/*出錯(cuò)處理*/
}
void func()
{
int res;
char* buf;
res = pthread_once(&once,createKey);
if(res != 0)
/*出錯(cuò)處理*/
buf = (char*)pthread_getspecific(key);
if(buf == NULL)
{
buf = (char*)malloc(BUFFSIZE);
pthread_setspecific(key,buf);
}
/*其他處理*/
}
線程取消
- 一般線程只能主動(dòng)退出或終結(jié),但是可以通過
int pthread_cancel(pthread_t thread)線程取消函數(shù)來終止一個(gè)指定線程。但是調(diào)用該取消函數(shù)會(huì)立即返回,這意味著指定的線程不會(huì)立即被取消,而線程何時(shí)取消以及會(huì)不會(huì)取消都要取決于該線程自身的線程取消狀態(tài)和類型屬性
- 線程可以調(diào)用
int pthread_setcancelstate(int state,int* oldstate)和int pthread_setcanceltype(int type,int* oldtype)來設(shè)置自身的取消狀態(tài)和類型,oldstate和oldtype緩沖區(qū)可以獲得原來的設(shè)定值。線這兩種屬性有以下的一些取值:線程取消狀態(tài)PTHREAD_CANCEL_DISABLE,表示線程不可取消,如果線程收到了取消請(qǐng)求,那么會(huì)將該請(qǐng)求掛起直到線程取消狀態(tài)改變;線程取消狀態(tài)PTHREAD_CANCEL_ENABLE,表示線程可以取消,這是線程取消狀態(tài)的默認(rèn)值,但是即使處于這個(gè)狀態(tài),也并不意味著線程會(huì)被取消。還需要取決于線程類型值(type)。如果type是PTHREAD_CANCEL_ASYNCHRONOUS,表示可能會(huì)在任意時(shí)間點(diǎn)取消線程,并不確定,這被稱之為異步取消;如果type是PTHREAD_CANCEL_DEFERED,取消請(qǐng)求會(huì)被掛起直到到達(dá)取消點(diǎn)(下面介紹取消點(diǎn))
- 取消點(diǎn)實(shí)際上就是一個(gè)函數(shù),如果線程收到了一個(gè)取消請(qǐng)求并且設(shè)置了取消狀態(tài)且取消類型被置為延遲,那么該線程就會(huì)被終止。另外,如果該線程沒有被分離,需要其他線程對(duì)其進(jìn)行連接,pthread_join調(diào)用的第二個(gè)參數(shù)會(huì)被置為PTHREAD_CANCELED
- 并不是所有的函數(shù)都是取消點(diǎn),如果線程的執(zhí)行函數(shù)中沒有取消點(diǎn),那么就不會(huì)響應(yīng)取消請(qǐng)求。這時(shí)候我們需要調(diào)用
void pthread_testcancel(void)函數(shù)來創(chuàng)造一個(gè)取消點(diǎn)。如果調(diào)用線程有一個(gè)取消請(qǐng)求被掛起,那么調(diào)用該函數(shù)會(huì)使得線程立即終止
線程和信號(hào)
- 信號(hào)屬于進(jìn)程層面,如果某一進(jìn)程收到一個(gè)信號(hào)且其默認(rèn)動(dòng)作為stop或者term,那么將停止整個(gè)進(jìn)程和所有線程
- 進(jìn)程中的所有線程共享為每個(gè)信號(hào)設(shè)置的信號(hào)處理程序
- 信號(hào)的發(fā)送既可以面向進(jìn)程也可以面向線程單獨(dú)發(fā)送。當(dāng)面向一個(gè)進(jìn)程發(fā)送信號(hào)時(shí),進(jìn)程會(huì)隨機(jī)選擇一個(gè)線程來接收該信號(hào)并處理。只有當(dāng)信號(hào)的來源是以下三種時(shí),才會(huì)面向線程發(fā)送信號(hào):其一是源于線程上下文中對(duì)特定硬件指令的執(zhí)行,即SIGBUS,SIGFPE,SIGILL,SIGSEGV;其二是跟管道相關(guān)的信號(hào)SIGPIPE;其三是通過pthread_kill或者pthread_sigqueue發(fā)出的信號(hào)
- 上面說過信號(hào)掩碼是線程獨(dú)有的,是針對(duì)每一個(gè)進(jìn)程而言的;可以調(diào)用
int pthread_sigmask(int how,const sigset_t* set,sigset_t* oldset)進(jìn)行設(shè)置。另外,內(nèi)核會(huì)為所有線程和進(jìn)程各自維護(hù)信號(hào)掛起集合,函數(shù)sigpending會(huì)返回為整個(gè)進(jìn)程和當(dāng)前線程所掛起信號(hào)的并集
- 函數(shù)
int pthread_kill(pthread_t thread,int sig)可以向指定線程發(fā)送信號(hào)而不是面向進(jìn)程發(fā)送信號(hào)
- 在多線程程序中調(diào)用exec后,將執(zhí)行程序替換并且只會(huì)留下調(diào)用線程,其他線程都會(huì)消失(包括所有的線程對(duì)象,比如互斥量,條件變量等);調(diào)用fork之后,子進(jìn)程只會(huì)復(fù)制調(diào)用線程而拋棄其他線程,但是會(huì)留下線程對(duì)象(這會(huì)出現(xiàn)一些問題,比如一些互斥量沒辦法解鎖)。所以在多線程程序中執(zhí)行fork一般都會(huì)在exec之后
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。