Linux之線程的創(chuàng)建方式
線程的概念與實(shí)現(xiàn)方式
線程是進(jìn)程內(nèi)部的一條執(zhí)行序列或執(zhí)行路徑,一個(gè)進(jìn)程可以包含多條線程。
- 從資源分配的角度來看,進(jìn)程是操作系統(tǒng)進(jìn)行資源分配的基本單位。
- 從資源調(diào)度的角度來看,線程是資源調(diào)度的最小單位,是程序執(zhí)行的最小單位
執(zhí)行序列就是一組有序指令的集合——函數(shù)。
線程是進(jìn)程內(nèi)部的一條執(zhí)行序列,一個(gè)進(jìn)程至少有一條線程,稱之為主線程(main方法代表的執(zhí)行序列),可以通過線程庫創(chuàng)建其他線程(給線程制定一個(gè)它要執(zhí)行的函數(shù)),將創(chuàng)建的線程稱之為函數(shù)線程。
線程的實(shí)現(xiàn)方式
- 內(nèi)核級(jí)線程(由內(nèi)核直接創(chuàng)建和管理線程,雖然創(chuàng)建開銷較大,但是可以利用多處理器的資源)
- 用戶級(jí)線程(由線程庫創(chuàng)建和管理多個(gè)線程,線程的實(shí)現(xiàn)都是在用戶態(tài),內(nèi)核無法感知,創(chuàng)建開銷較小,無法使用多處理器的資源)
- 混合級(jí)線程(結(jié)合以上兩種方式實(shí)現(xiàn),可以利用多處理器的資源,從而在用戶空間中創(chuàng)建更多的線程,從而映射到內(nèi)核空間的線程中,多對(duì)多,N:M(N>>M))
Linux系統(tǒng)實(shí)現(xiàn)多線程的方式
Linux 實(shí)現(xiàn)線程的機(jī)制非常獨(dú)特。從內(nèi)核的角度來說,它并沒有線程這個(gè)概念。
Linux 把所有的線程都當(dāng)做進(jìn)程來實(shí)現(xiàn)。內(nèi)核并沒有準(zhǔn)備特別的調(diào)度算法或是定義特別的數(shù)據(jù)結(jié)構(gòu)來表征線程。
相反,線程僅僅被視為一個(gè)與其他進(jìn)程共享某些資源的進(jìn)程。
每個(gè)線程都擁有唯 一隸屬于自己的task_struct,所以在內(nèi)核中,它看起來就像是一個(gè)普通的進(jìn)程(只是線程和 其他一些進(jìn)程共享某些資源,如地址空間)
線程和進(jìn)程的區(qū)別
- 進(jìn)程是資源分配最小單位,線程是程序執(zhí)行的最小單位;
- 線程間的切換效率相比進(jìn)程間的切換要高
- 進(jìn)程有自己獨(dú)立的地址空間,每啟動(dòng)一個(gè)進(jìn)程,系統(tǒng)都會(huì)為其分配地址空間,建立數(shù)據(jù)表來維護(hù)代碼段、堆棧段和數(shù)據(jù)段,線程沒有獨(dú)立的地址空間,它使用相同的地址空間共享數(shù)據(jù);
- 創(chuàng)建一個(gè)線程比進(jìn)程開銷??;
- 線程占用的資源要?進(jìn)程少很多。
- 線程之間通信更方便,同一個(gè)進(jìn)程下,線程共享全局變量,靜態(tài)變量等數(shù)據(jù),進(jìn)程之間的通信需要以通信的方式(IPC)進(jìn)行;(但多線程程序處理好同步與互斥是個(gè)難點(diǎn))
- 多進(jìn)程程序更安全,生命力更強(qiáng),一個(gè)進(jìn)程死掉不會(huì)對(duì)另一個(gè)進(jìn)程造成影響(源于有獨(dú)立的地址空間),多線程程序更不易維護(hù),一個(gè)線程死掉,整個(gè)進(jìn)程就死掉了(因?yàn)楣蚕淼刂房臻g);
- 進(jìn)程對(duì)資源保護(hù)要求高,開銷大,效率相對(duì)較低,線程資源保護(hù)要求不高,但開銷小,效率高,可頻繁切換;
多線程開發(fā)的三個(gè)基本概念
- 線程 【創(chuàng)建、退出、等待】
- 互斥鎖【創(chuàng)建、銷毀、加鎖】、解鎖】
- 條件【創(chuàng)建、銷毀、觸發(fā)、廣播、等待】
線程庫的使用
1.創(chuàng)建線程
#include<phread.h> int pthread_create(pthread_t *id , pthread_attr_t *attr, void(*fun)(void*), void *arg);
id
:傳遞一個(gè)pthread_t類型的變量的地址,創(chuàng)建成功后,用來獲取新創(chuàng)建的線程的TIDattr
:指定線程的屬性 默認(rèn)使用NULLfun
:線程函數(shù)的地址arg
:傳遞給線程函數(shù)的參數(shù)- 返回值,成功返回0,失敗返回錯(cuò)誤碼
多線程代碼示例
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> #include<pthread.h> //聲明一個(gè)線程函數(shù) void *fun(void *); int main() { printf("main start\n"); pthread_t id; //創(chuàng)建函數(shù)線程,并且指定函數(shù)線程要執(zhí)行的函數(shù) int res = pthread_create(&id,NULL,fun,NULL); assert(res == 0); //之后并發(fā)運(yùn)行 int i = 0; for(; i < 5; i++) { printf("main running\n"); sleep(1); } printf("main over\n"); exit(0); } //定義線程函數(shù) void* fun(void *arg) { printf("fun start\n"); int i = 0; for(; i < 3;i++) { printf("fun running\n"); sleep(1); } printf("fun over\n"); }
gcc編譯代碼時(shí)報(bào)`undifined reference to xxxxx錯(cuò)誤,都是因?yàn)槌绦蛑姓{(diào)用了一些方法,但是沒有連接該方法所在的文件,例如下面的情況:
連接庫文件編譯成功并執(zhí)行,這一點(diǎn)在幫助手冊(cè)中也有提示:Compile and link with -pthread
比較兩次運(yùn)行的結(jié)果發(fā)現(xiàn)前三條執(zhí)行語句時(shí)一樣的
結(jié)論
- 創(chuàng)建線程并執(zhí)行線程函數(shù),和調(diào)用函數(shù)是完全不同的概念。
- 主線程和函數(shù)線程是并發(fā)執(zhí)行的。
- 線程提前于主線程結(jié)束時(shí),不會(huì)影響主線程的運(yùn)行
- 主線程提前于線程結(jié)束時(shí),整個(gè)進(jìn)程都會(huì)結(jié)束,其他線程也會(huì)結(jié)束
- 創(chuàng)建函數(shù)線程后,哪個(gè)線程先被執(zhí)行是有操作系統(tǒng)的調(diào)度算法和機(jī)器環(huán)境決定。
函數(shù)線程在主線程結(jié)束后也隨之退出,原因:主線程結(jié)束時(shí)使用的是exit方法,這個(gè)方法結(jié)束的是進(jìn)程。
然而修改代碼為:pthread_exit(NULL);
此時(shí)主線程結(jié)束,函數(shù)線程會(huì)繼續(xù)執(zhí)行直至完成。即便如此,我們還是不推薦大家手動(dòng)結(jié)束主線程,我們更喜歡讓主線程等待一會(huì)。
給線程函數(shù)傳參
①值傳遞
將變量的值直接轉(zhuǎn)成void*類型進(jìn)行傳遞
因?yàn)榫€程函數(shù)接受的是一個(gè)void*類型的指針,只要是指針,32位系統(tǒng)上都是4個(gè)字節(jié),值傳遞就只能傳遞小于或等于4字節(jié)的值。
代碼示例
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> #include<pthread.h> void *fun(void *); int main() { printf("main start\n"); int a = 10; pthread_t id; int res = pthread_create(&id,NULL,fun,(void*)a); assert(res == 0); int i = 0; for(; i < 5; i++) { printf("main running\n"); sleep(1); } printf("main over\n"); exit(0); } void* fun(void *arg) { int b = (int)arg; printf("b == %d\n",b); }
②地址傳遞
將變量(所有類型)的地址強(qiáng)轉(zhuǎn)成void*
類型進(jìn)行傳遞,就和在普通函數(shù)調(diào)用傳遞變量的地址相似。
主線程和函數(shù)線程通過這個(gè)地址就可以共享地址所指向的空間。
一個(gè)進(jìn)程內(nèi)的所有線程是共享這個(gè)進(jìn)程的地址空間。
多線程下進(jìn)程的4G虛擬地址空間
一個(gè)進(jìn)程內(nèi)的所有線程對(duì)于全局?jǐn)?shù)據(jù),靜態(tài)數(shù)據(jù),堆區(qū)空間都是共享的。
線程之間傳遞數(shù)據(jù)很簡(jiǎn)單,但是隨之帶來的問題就是線程并發(fā)運(yùn)行時(shí)無法保證線程安全。
代碼示例
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> #include<pthread.h> int gdata = 10; //.data void *fun(void *); int main() { int *ptr = (int *)malloc(4);//.heap *ptr = 10; pthread_t id; int res = pthread_create(&id,NULL,fun,(void*)ptr); assert(res == 0); sleep(2);//等待兩秒,保證函數(shù)線程已經(jīng)講數(shù)據(jù)修改 printf("main : gdata == %d\n",gdata); printf("main : *ptr = %d\n",*ptr); exit(0); } void *fun(void *arg) { int *p = (int*)arg; gdata = 20000; *p = 20; printf("fun over\n"); }
線程庫中的其他方法
線程退出的三種方式:
- 線程從執(zhí)行函數(shù)返回,返回值是線程的退出碼;
- 線程被同一進(jìn)程的其他線程取消;
- 調(diào)用pthread_exit()函數(shù)退出;
等待線程終止
int pthread_join(pthread_t thread, void **retval); args: pthread_t thread: 被連接線程的線程號(hào),該線程必須位于當(dāng)前進(jìn)程中,而且不得是分離線程 void **retval :該參數(shù)不為NULL時(shí),指向某個(gè)位置 在該函數(shù)返回時(shí),將該位置設(shè)置為已終止線程的退出狀態(tài) return: 線程連接的狀態(tài),0是成功,非0是失敗
當(dāng)A線程調(diào)用線程B并 pthread_join() 時(shí),A線程會(huì)處于阻塞狀態(tài),直到B線程結(jié)束后,A線程才會(huì)繼續(xù)執(zhí)行下去。當(dāng) pthread_join() 函數(shù)返回后,被調(diào)用線程才算真正意義上的結(jié)束,它的內(nèi)存空間也會(huì)被釋放(如果被調(diào)用線程是非分離的)。
這里有三點(diǎn)需要注意:
- 1.被釋放的內(nèi)存空間僅僅是系統(tǒng)空間,你必須手動(dòng)清除程序分配的空間,比如 malloc() 分配的空間。
- 2.一個(gè)線程只能被一個(gè)線程所連接。
- 3.被連接的線程必須是非分離的,否則連接會(huì)出錯(cuò)。所以可以看出pthread_join()有兩種作用:1-用于等待其他線程結(jié)束:當(dāng)調(diào)用 pthread_join() 時(shí),當(dāng)前線程會(huì)處于阻塞狀態(tài),直到被調(diào)用的線程結(jié)束后,當(dāng)前線程才會(huì)重新開始執(zhí)行。2-對(duì)線程的資源進(jìn)行回收:如果一個(gè)線程是非分離的(默認(rèn)情況下創(chuàng)建的線程都是非分離)并且沒有對(duì)該線程使用 pthread_join() 的話,該線程結(jié)束后并不會(huì)釋放其內(nèi)存空間,這會(huì)導(dǎo)致該線程變成了“僵尸線程”。
等待指定的子線程結(jié)束
- 等待thread()指定的線程退出,線程未退出時(shí),該方法阻塞
- result接收thread線程退出時(shí),指定退出信息
int pthread_join(pthread_t id,void **result)//調(diào)用這個(gè)方法的線程會(huì)阻塞,直到等待線程結(jié)束
代碼演示:
#include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h> #include<pthread.h> int main() { printf("main start\n"); pthread_t id; int res = pthread_create(&id,NULL,fun,NULL); assert(res == 0); //之后并發(fā)運(yùn)行 int i = 0; for(; i < 5; i++) { printf("main running\n"); sleep(1); } char *s = NULL; pthread_join(id,(void **)&s); printf("join : s = %s\n",s); exit(0); } //定義線程函數(shù) void* fun(void *arg) { printf("fun start\n"); int i = 0; for(; i < 10;i++) { printf("fun running\n"); sleep(1); } printf("fun over\n"); pthread_exit("fun over");//將該字符常量返回給主線程 }
此時(shí),主線程完成五次輸出,就會(huì)等待子線程結(jié)束,阻塞等待,子線程結(jié)束后,最后,主線程打印join:s = fun over
關(guān)于exit和join的一些詳細(xì)說明:
- 線程自己運(yùn)行結(jié)束,或者調(diào)用pthread_exit結(jié)束,線程都會(huì)釋放自己獨(dú)有的空間資源;
- 若線程是非分離的,線程會(huì)保留線程ID號(hào),直到其他線程通過joining這個(gè)線程確認(rèn)其已經(jīng)死亡,join的結(jié)果是joining線程得到已終止線程的退出狀態(tài),已終止線程將消失;
- 若線程是分離的,不需要使用pthread_exit(),線程自己運(yùn)行結(jié)束,線程結(jié)束就會(huì)自己釋放所有空間資源(包括線程ID號(hào));
- 子線程最終一定要使用pthread_join()或者設(shè)置為分離線程來結(jié)束線程,否則線程的資源不會(huì)被完全釋放(使用取消線程功能也不能完全釋放);
- 主線程運(yùn)行pthrea_exit(),會(huì)結(jié)束主線程,但是不會(huì)結(jié)束子線程;
- 主線程結(jié)束,則整個(gè)程序結(jié)束,所以主線程最好使用pthread_join函數(shù)等待子線程結(jié)束,使用該函數(shù)一個(gè)線程可以等待多個(gè)線程結(jié)束;
- 使用pthread_join函數(shù)的線程將會(huì)阻塞,直到被join的函數(shù)線程結(jié)束,該函數(shù)返回,但是它對(duì)被等待終止的線程運(yùn)行沒有影響;
- 如果子線程使用exit()則可以結(jié)束整個(gè)進(jìn)程;
線程屬性
線程具有的屬性可以在線程創(chuàng)建的時(shí)候指定;
——pthread_create()函數(shù)的第二個(gè)參數(shù)(pthread_attr_t *attr)表示線程的屬性,在以前的例子中將其值設(shè)為NULL,也就是采用默認(rèn)屬性,線程的多項(xiàng)屬性都是可以修改的,這些屬性包括綁定屬性,分離屬性,堆棧屬性,堆棧大小,優(yōu)先級(jí)。
系統(tǒng)默認(rèn)的是非綁定,非分離,缺省1M的堆棧以及父子進(jìn)程優(yōu)先級(jí)相同
線程結(jié)構(gòu)如下:
typedef struct { int detachstate; //線程的分離狀態(tài) int schedpolicy; //線程調(diào)度策略 struct sched_param schedparam; //線程的調(diào)度參數(shù) int inheritsched; //線程的繼承性 int scope; //線程的作用域 size_t guardsize; //線程棧末尾的警戒緩沖區(qū)大小 int stackaddr_set; //線程的棧設(shè)置 void* stackaddr; //線程棧的位置 size_t stacksize; //線程棧的大小 } pthread_attr_t;
每一個(gè)屬性都有對(duì)應(yīng)的一些函數(shù),用于對(duì)其進(jìn)行查看和修改,下面分別介紹:
線程屬性初始化
初始化和去初始化分別對(duì)應(yīng)于如下的兩個(gè)函數(shù):
#include <pthread.h> ? ①int pthread_attr_init(pthread_attr_t *attr); ②it pthread_attr_destroy(pthread_attr_t *attr);
①功能:
- 初始化線程屬性函數(shù),注意:應(yīng)先初始化線程屬性,再pthread_create創(chuàng)建線程
參數(shù):
attr
:線程屬性結(jié)構(gòu)體
返回值:
- 成功:0
- 失?。?1
②?功能:
- 銷毀線程屬性所占用的資源函數(shù)
參數(shù):
attr
:線程屬性結(jié)構(gòu)體
返回值:
- 成功:0
- 失?。?1
線程分離
線程的分離狀態(tài)決定一個(gè)線程以什么樣的方式來終止自己,這個(gè)在之前我們也說過了。
- 非分離狀態(tài):線程的默認(rèn)屬性是非分離狀態(tài),這種情況下,原有的線程等待創(chuàng)建的線程結(jié)束。只有當(dāng)pthread_join()函數(shù)返回時(shí),創(chuàng)建的線程才算終止,才能釋放自己占用的系統(tǒng)資源。
- 分離狀態(tài):分離線程沒有被其他的線程所等待,自己運(yùn)行結(jié)束了,線程也就終止了,馬上釋放系統(tǒng)資源。應(yīng)該根據(jù)自己的需要,選擇適當(dāng)?shù)姆蛛x狀態(tài)。
相關(guān)API如下:
#include <pthread.h> ? int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
功能:設(shè)置線程分離狀態(tài)
參數(shù):
attr
:已初始化的線程屬性detachstate
: 分離狀態(tài)
PTHREAD_CREATE_DETACHED(分離線程)
PTHREAD_CREATE_JOINABLE(非分離線程)
返回值:
- 成功:0
- 失?。悍?
?
int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);
功能:獲取線程分離狀態(tài)
參數(shù):
attr
:已初始化的線程屬性detachstate: 分離狀態(tài)
PTHREAD_CREATE_DETACHED(分離線程)
PTHREAD _CREATE_JOINABLE(非分離線程)
返回值:
- 成功:0
- 失?。悍?
注意:
當(dāng)一個(gè)線程被設(shè)置為分離線程時(shí),假設(shè)此時(shí)該線程的執(zhí)行速度非??欤芸赡茉趐thread_create返回之前就終止; 終止之后將線程號(hào)和系統(tǒng)資源移交給其他線程使用,這樣調(diào)用create就得到了錯(cuò)誤的線程號(hào),因此就必須采取一些同步措施,可以在被創(chuàng)建的線程里調(diào)用pthread_cond_timedwait函數(shù),讓這個(gè)線程等待一會(huì)兒,留出足夠的時(shí)間讓函數(shù)pthread_create返回,設(shè)置一段等待時(shí)間,是在多線程編程里常用的方法。但是注意不要使用諸如wait()之類的函數(shù),它們是使整個(gè)進(jìn)程睡眠,并不能解決線程同步的問題。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
使用Apache ab進(jìn)行http性能測(cè)試
這篇文章主要為大家詳細(xì)介紹了使用Apache ab進(jìn)行http性能測(cè)試,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-12-12centos下簡(jiǎn)單配置安裝Squid 3.0反向代理
以下安裝及配置均為Sudu所在公司應(yīng)用中使用的腳本,希望高手們能指正我的一些不足。如果測(cè)試過程中發(fā)現(xiàn)了問題請(qǐng)回復(fù)或者聯(lián)系我。2010-12-12詳解apache編譯安裝httpd-2.4.54及三種風(fēng)格的init程序特點(diǎn)和區(qū)別
這篇文章主要介紹了apache編譯安裝httpd-2.4.54以及三種風(fēng)格的init程序特點(diǎn)和區(qū)別?,通過編譯安裝httpd來深入理解源碼包安裝(httpd-2.4.54),本文給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07centos7下搭建ZooKeeper3.4中間件常用命令小結(jié)
Zookeeper 作為一個(gè)分布式的服務(wù)框架,主要用來解決分布式集群中應(yīng)用系統(tǒng)的一致性問題,它能提供基于類似于文件系統(tǒng)的目錄節(jié)點(diǎn)樹方式的數(shù)據(jù)存儲(chǔ)。這篇文章主要介紹了centos7下搭建ZooKeeper3.4中間件常用命令小結(jié),需要的朋友可以參考下2019-07-07