Linux線程的創建方式是什么

    線程的概念與實現方式

    線程是進程內部的一條執行序列或執行路徑,一個進程可以包含多條線程。

    • 從資源分配的角度來看,進程是操作系統進行資源分配的基本單位。

    • 從資源調度的角度來看,線程是資源調度的最小單位,是程序執行的最小單位

    執行序列就是一組有序指令的集合——函數。

    線程是進程內部的一條執行序列,一個進程至少有一條線程,稱之為主線程(main方法代表的執行序列),可以通過線程庫創建其他線程(給線程制定一個它要執行的函數),將創建的線程稱之為函數線程。

    Linux線程的創建方式是什么

    線程的實現方式

    • 內核級線程(由內核直接創建和管理線程,雖然創建開銷較大,但是可以利用多處理器的資源)

    • 用戶級線程(由線程庫創建和管理多個線程,線程的實現都是在用戶態,內核無法感知,創建開銷較小,無法使用多處理器的資源)

    • 混合級線程(結合以上兩種方式實現,可以利用多處理器的資源,從而在用戶空間中創建更多的線程,從而映射到內核空間的線程中,多對多,N:M(N>>M))

    Linux線程的創建方式是什么

    linux系統實現多線程的方式

    Linux 實現線程的機制非常獨特。從內核的角度來說,它并沒有線程這個概念。

    Linux 把所有的線程都當做進程來實現。內核并沒有為表征線程準備特別的調度算法或定義特別的數據結構

    相反,線程僅僅被視為一個與其他進程共享某些資源的進程。

    每個線程都擁有唯 一隸屬于自己的task_struct,所以在內核中,它看起來就像是一個普通的進程(只是線程和 其他一些進程共享某些資源,如地址空間)

    線程和進程的區別

    • 進程是資源分配最小單位,線程是程序執行的最小單位;

    • 線程間的切換效率相比進程間的切換要高

    • 進程有自己獨立的地址空間,每啟動一個進程,系統都會為其分配地址空間,建立數據表來維護代碼段、段和數據段,線程沒有獨立的地址空間,它使用相同的地址空間共享數據;

    • 創建一個線程比進程開銷小;

    • 線程占用的資源要?進程少很多。

    • 線程之間通信更方便,同一個進程下,線程共享全局變量,靜態變量等數據,進程之間的通信需要以通信的方式(IPC)進行;(但多線程程序處理好同步與互斥是個難點)

    • 多進程程序更安全,生命力更強,一個進程死掉不會對另一個進程造成影響(源于有獨立的地址空間),多線程程序更不易維護,一個線程死掉,整個進程就死掉了(因為共享地址空間);

    • 進程對資源保護要求高,開銷大,效率相對較低,線程資源保護要求不高,但開銷小,效率高,可頻繁切換;

    多線程開發的三個基本概念

    • 線程 【創建、退出、等待】

    • 互斥鎖【創建、銷毀、加鎖】、解鎖】

    • 條件【創建、銷毀、觸發、廣播、等待】

    線程庫的使用

    1.創建線程

    #include<phread.h>  int?pThread_create(pthread_t?*id?,?pthread_attr_t?*attr,?void(*fun)(void*),?void?*arg);</phread.h>
    • id :傳遞一個pthread_t類型的變量的地址,創建成功后,用來獲取新創建的線程的TID

    • attr:指定線程的屬性 默認使用NULL

    • fun:線程函數的地址

    • arg:傳遞給線程函數的參數

    • 返回值,成功返回0,失敗返回錯誤碼

    多線程代碼示例

    #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?startn");  	pthread_t?id; 	//創建函數線程,并且指定函數線程要執行的函數 	int?res?=?pthread_create(&amp;id,NULL,fun,NULL); 	assert(res?==?0);  	//之后并發運行 	int?i?=?0;	 	for(;?i?<p>gcc編譯代碼時報`undifined reference to xxxxx錯誤,都是因為程序中調用了一些方法,但是沒有連接該方法所在的文件,例如下面的情況:</p><p><img src="https://img.php.cn/upload/article/000/465/014/168475189468586.png" alt="Linux線程的創建方式是什么"></p><p>連接庫文件編譯成功并執行,這一點在幫助手冊中也有提示:Compile and link with -pthread</p><p><img src="https://img.php.cn/upload/article/000/465/014/168475189431918.png" alt="Linux線程的創建方式是什么"></p><p>比較兩次運行的結果發現前三條執行語句時一樣的</p><p><img src="https://img.php.cn/upload/article/000/465/014/168475189410716.png" alt="Linux線程的創建方式是什么"></p><p><strong>結論</strong></p><ul class=" list-paddingleft-2"> <li><p>創建線程并執行線程函數,和調用函數是完全不同的概念。</p></li> <li><p>主線程和函數線程是并發執行的。</p></li> <li><p>線程提前于主線程結束時,不會影響主線程的運行</p></li> <li><p>主線程提前于線程結束時,整個進程都會結束,其他線程也會結束</p></li> <li><p>創建函數線程后,哪個線程先被執行是有操作系統的調度算法和機器環境決定。</p></li> </ul><p><img src="https://img.php.cn/upload/article/000/465/014/168475189413184.png" alt="Linux線程的創建方式是什么"></p><p>函數線程在主線程結束后也隨之退出,原因:主線程結束時使用的是exit方法,這個方法結束的是進程。</p><p>然而修改代碼為:pthread_exit(NULL);此時主線程結束,函數線程會繼續執行直至完成。即便如此,我們還是不推薦大家手動結束主線程,我們更喜歡讓主線程等待一會。</p><p><strong>給線程函數傳參</strong></p><p>①值傳遞</p><p>將變量的值直接轉成void*類型進行傳遞</p><p>因為線程函數接受的是一個void*類型的指針,只要是指針,32位系統上都是4個字節,值傳遞就只能傳遞小于或等于4字節的值。</p><p><strong>代碼示例</strong></p><pre class="brush:cpp;">#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?startn");  	int?a?=?10; 	 	pthread_t?id; 	int?res?=?pthread_create(&amp;id,NULL,fun,(void*)a); 	assert(res?==?0);  	int?i?=?0;	 	for(;?i?<p><img src="https://img.php.cn/upload/article/000/465/014/168475189537043.png" alt="Linux線程的創建方式是什么"></p><p>②地址傳遞</p><p>將變量(所有類型)的地址強轉成void*類型進行傳遞,就和在普通函數調用傳遞變量的地址相似。</p><p>主線程和函數線程通過這個地址就可以<strong>共享</strong>地址所指向的空間。</p><p><strong>一個進程內的所有線程是共享這個進程的地址空間。</strong></p><p>多線程下進程的4G虛擬地址空間</p><p><img src="https://img.php.cn/upload/article/000/465/014/168475189582591.png" alt="Linux線程的創建方式是什么"></p><p>一個進程內的所有線程對于全局數據,靜態數據,堆區空間都是共享的。</p><p>線程之間傳遞數據很簡單,但是隨之帶來的問題就是線程并發運行時無法保證線程安全。</p><p><strong>代碼示例</strong></p><pre class="brush:cpp;">#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(&amp;id,NULL,fun,(void*)ptr); 	assert(res?==?0);  ????sleep(2);//等待兩秒,保證函數線程已經講數據修改  	printf("main?:?gdata?==?%dn",gdata); ????printf("main?:?*ptr?=?%dn",*ptr);  	exit(0); }   void?*fun(void?*arg) { 	int?*p?=?(int*)arg;  ????gdata?=?20000; ????*p?=?20;  	printf("fun?overn"); }</pthread.h></unistd.h></string.h></assert.h></stdlib.h></stdio.h>

    Linux線程的創建方式是什么

    線程庫中的其他方法

    線程退出的三種方式:

    • 線程從執行函數返回,返回值是線程的退出碼;

    • 線程被同一進程的其他線程取消;

    • 調用pthread_exit()函數退出;

    等待線程終止

    int?pthread_join(pthread_t?thread,?void?**retval); args: ????pthread_t?thread:?被連接線程的線程號,該線程必須位于當前進程中,而且不得是分離線程 ????void?**retval?:該參數不為NULL時,指向某個位置?在該函數返回時,將該位置設置為已終止線程的退出狀態 ????return: ????線程連接的狀態,0是成功,非0是失敗

    當A線程調用線程B并 pthread_join() 時,A線程會處于阻塞狀態,直到B線程結束后,A線程才會繼續執行下去。當 pthread_join() 函數返回后,被調用線程才算真正意義上的結束,它的內存空間也會被釋放(如果被調用線程是非分離的)。

    這里有三點需要注意:

    • 系統僅釋放系統空間,你需要手動清除程序分配的空間,例如由 malloc() 分配的空間。

    • 2.一個線程只能被一個線程所連接。

    • 3.被連接的線程必須是非分離的,否則連接會出錯。所以可以看出pthread_join()有兩種作用:1-用于等待其他線程結束:當調用 pthread_join() 時,當前線程會處于阻塞狀態,直到被調用的線程結束后,當前線程才會重新開始執行。2-對線程的資源進行回收:如果一個線程是非分離的(默認情況下創建的線程都是非分離)并且沒有對該線程使用 pthread_join() 的話,該線程結束后并不會釋放其內存空間,這會導致該線程變成了“僵尸線程”。

    等待指定的子線程結束

    • 等待thread()指定的線程退出,線程未退出時,該方法阻塞

    • result接收thread線程退出時,指定退出信息

    int?pthread_join(pthread_t?id,void?**result)//調用這個方法的線程會阻塞,直到等待線程結束

    代碼演示:

    #include<stdio.h> #include<stdlib.h> #include<assert.h> #include<string.h> #include<unistd.h>  #include<pthread.h>  int?main() { 	printf("main?startn");  	pthread_t?id; 	int?res?=?pthread_create(&amp;id,NULL,fun,NULL); 	assert(res?==?0);  	//之后并發運行 	int?i?=?0;	 	for(;?i?<p>此時,主線程完成五次輸出,就會等待子線程結束,阻塞等待,子線程結束后,最后,主線程打印join:s = fun over</p><p><strong>關于exit和join的一些詳細說明:</strong></p><ul class=" list-paddingleft-2"> <li><p>線程自己運行結束,或者調用pthread_exit結束,線程都會釋放自己獨有的空間資源;</p></li> <li><p>若線程是非分離的,線程會保留線程ID號,直到其他線程通過joining這個線程確認其已經死亡,join的結果是joining線程得到已終止線程的退出狀態,已終止線程將消失;</p></li> <li><p>若線程是分離的,不需要使用pthread_exit(),線程自己運行結束,線程結束就會自己釋放所有空間資源(包括線程ID號);</p></li> <li><p>子線程最終一定要使用pthread_join()或者設置為分離線程來結束線程,否則線程的資源不會被完全釋放(使用取消線程功能也不能完全釋放);</p></li> <li><p>主線程運行pthrea_exit(),會結束主線程,但是不會結束子線程;</p></li> <li><p>主線程結束,則整個程序結束,所以主線程最好使用pthread_join函數等待子線程結束,使用該函數一個線程可以等待多個線程結束;</p></li> <li><p>使用pthread_join函數的線程將會阻塞,直到被join的函數線程結束,該函數返回,但是它對被等待終止的線程運行沒有影響;</p></li> <li><p>如果子線程使用exit()則可以結束整個進程;</p></li> </ul><h3>線程屬性</h3><p>線程具有的屬性可以在線程創建的時候指定;</p><p>——pthread_create()函數的第二個參數(pthread_attr_t *attr)表示線程的屬性,在以前的例子中將其值設為NULL,也就是采用默認屬性,線程的多項屬性都是可以修改的,這些屬性包括綁定屬性,分離屬性,堆棧屬性,堆棧大小,優先級。</p><p>系統默認的是非綁定,非分離,缺省1M的堆棧以及父子進程優先級相同</p><p>線程結構如下:</p><pre class="brush:cpp;">typedef?struct { ????int?????????????detachstate;?????//線程的分離狀態 ????int?????????????schedpolicy;????//線程調度策略 ????struct?sched_param??schedparam;?//線程的調度參數 ????int?????????????inheritsched;???//線程的繼承性 ????int?????????????scope;??????//線程的作用域 ????size_t??????????guardsize;??//線程棧末尾的警戒緩沖區大小 ????int?????????????stackaddr_set;?//線程的棧設置 ????void*???????????stackaddr;??//線程棧的位置 ????size_t??????????stacksize;??//線程棧的大小 }?pthread_attr_t;

    每一個屬性都有對應的一些函數,用于對其進行查看和修改,下面分別介紹:

    線程屬性初始化

    初始化和去初始化分別對應于如下的兩個函數:

    #include?<pthread.h>  ①int?pthread_attr_init(pthread_attr_t?*attr); ②it?pthread_attr_destroy(pthread_attr_t?*attr);</pthread.h>

    ①功能:

    • 初始化線程屬性函數,注意:應先初始化線程屬性,再pthread_create創建線程

    參數:

    返回值:

    • 成功:0

    • 失敗:-1

    ②功能:

    • 銷毀線程屬性所占用的資源函數

    參數:

    • attr:線程屬性結構體

    返回值:

    • 成功:0

    • 失敗:-1

    線程分離

    線程的分離狀態決定一個線程以什么樣的方式來終止自己,這個在之前我們也說過了。

    • 默認狀態下,線程是非分離狀態,意味著原有的線程會等待所創建的線程結束。只有在pthread_join()函數返回后,才能釋放創建的線程占用的系統資源,也才能視作該線程終止。

    • 若線程運行結束且無其他線程阻塞等待,則該線程處于分離狀態,此時系統資源將立即被釋放。應該根據自己的需要,選擇適當的分離狀態。

    相關API如下:

    #include?<pthread.h>  int?pthread_attr_setdetachstate(pthread_attr_t?*attr,?int?detachstate);</pthread.h>

    功能:設置線程分離狀態

    參數:

    • attr:已初始化的線程屬性

    • detachstate: 分離狀態

    PTHREAD_CREATE_DETACHED(分離線程)PTHREAD_CREATE_JOINABLE(非分離線程)

    返回值:

    • 成功:0

    • 失敗:非0

    int?pthread_attr_getdetachstate(const?pthread_attr_t?*attr,?int?*detachstate);

    功能:獲取線程分離狀態

    參數:

    • attr:已初始化的線程屬性detachstate: 分離狀態

    PTHREAD_CREATE_DETACHED(分離線程)

    PTHREAD _CREATE_JOINABLE(非分離線程)

    返回值:

    • 成功:0

    • 失敗:非0

    注意:

    當一個線程被設置為分離線程時,假設此時該線程的執行速度非常快,它很可能在pthread_create返回之前就終止; 終止之后將線程號和系統資源移交給其他線程使用,這樣調用create就得到了錯誤的線程號,因此就必須采取一些同步措施,可以在被創建的線程里調用pthread_cond_timedwait函數,讓這個線程等待一會兒,留出足夠的時間讓函數pthread_create返回,設置一段等待時間,是在多線程編程里常用的方法。要避免使用像wait()這樣的函數,因為它們會讓整個進程進入睡眠狀態,而無法解決線程同步問題。

    ? 版權聲明
    THE END
    喜歡就支持一下吧
    點贊14 分享