日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > linux >内容正文

linux

Linux线程-概念和控制

發(fā)布時間:2023/12/31 linux 39 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux线程-概念和控制 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

Linux線程-概念和控制

  • 零、前言
  • 一、Linux線程概念
    • 1、什么是線程
    • 2、vfork函數(shù)/pthread線程庫
    • 3、線程優(yōu)缺點及其他分析
  • 二、Linux進程VS線程
    • 1、進程和線程
  • 三、Linux線程控制
    • 1、POSIX線程庫
    • 2、線程創(chuàng)建
    • 3、線程ID及線程地址空間布局
    • 4、線程終止
    • 5、線程等待
    • 6、線程分離

零、前言

本章主要講解學習Linux中的線程

一、Linux線程概念

1、什么是線程

  • 概念:
  • 在一個程序里的一個執(zhí)行路線就叫做線程(thread),更準確的定義是:線程是“一個進程內(nèi)部的控制序列”

  • 一切進程至少都有一個執(zhí)行線程,也就是主線程,進程由一個或者多個線程組成,即進程中可以有多個執(zhí)行流

  • 線程是進程的一個執(zhí)行分支,實在進程內(nèi)部運行的一個執(zhí)行流,本質(zhì)是在進程地址空間內(nèi)運行,共享進程的進程地址空間,執(zhí)行進程的一部分代碼

    • 以整個運行視角理解:
  • 程序運行,將代碼和數(shù)據(jù)加載到CPU上,同時系統(tǒng)創(chuàng)建對應(yīng)的進程進行承擔分配系統(tǒng)資源,如創(chuàng)建task_struct結(jié)構(gòu)體,構(gòu)建對應(yīng)的進程地址空間,頁表建立虛擬地址與物理地址的映射等等,即進程是承擔分配系統(tǒng)資源的基本單元

  • 在進程中可能存在多個執(zhí)行流(一定有個主執(zhí)行流),也就是線程,而這些執(zhí)行流都是由task_struct描述的,共享同一個進行地址空間,透過進程虛擬地址空間,可以看到進程的大部分資源,將進程資源合理分配給每個執(zhí)行流,就形成了線程執(zhí)行流,執(zhí)行程序的部分代碼,這些執(zhí)行流可以進行并發(fā)執(zhí)行,由于是在進行內(nèi)部運行,不用切換整個進程的上下文數(shù)據(jù),只需切換線程的上下文數(shù)據(jù),即線程是系統(tǒng)調(diào)度的基本單元

    • 示圖:

    注:在Linux系統(tǒng)下的CPU眼中,看到的PCB(task_struct)都要比傳統(tǒng)的進程更加輕量化

    • 如何理解之前所說的’進程’:

    進程是一個大的整體,包括task_struct(PCB),進程地址空間、文件、信號等,是承擔分配系統(tǒng)資源的基本實體,而之前所受的進程都只有一個task_struct,也就是該進程內(nèi)部只有一個執(zhí)行流

    • 注意:
  • 在Linux中,CPU只關(guān)心一個一個的獨立執(zhí)行流,無論進程內(nèi)部只有一個執(zhí)行流還是有多個執(zhí)行流,CPU都是以task_struct為單位進行調(diào)度的

  • Linux下并不存在真正的多線程,而是用進程模擬的。如果要支持真的線程(TCB)會提高操作系統(tǒng)的復雜程度。而線程的和進程的控制塊基本是類似實現(xiàn)的,因此Linux直接復用了進程控制塊,所以Linux中的所有執(zhí)行流都叫做輕量級進程

  • 在Linux中都沒有真正意義的線程,所以也就沒有真正意義上的線程相關(guān)的系統(tǒng)調(diào)用,但是Linux提供了輕量級進程相關(guān)的庫和接口,例如vfork函數(shù)和原生線程庫pthread

  • 2、vfork函數(shù)/pthread線程庫

    • vfork函數(shù)原型:
    pid_t vfork(void);
    • 注意:
  • 功能:創(chuàng)建子進程,但是父子共享進程地址空間

  • 返回值:成功給父進程返回子進程的PID;給子進程返回0

  • 示例:

    #include<stdio.h> #include<unistd.h> #include<sys/types.h>int main() {int val=100;pid_t id=vfork();if(id==0){//childint cnt=0;while(1){printf("i am child pid:%d ppid:%d val:%d &val:%p\n",getpid(),getppid(),val,&val);cnt++;sleep(1);if(cnt==2)val=200;if(cnt==5)exit(0);}}else if(id>0){//fatherint cnt=0;while(1){printf("i am father pid:%d ppid:%d val:%d &val:%p\n",getpid(),getppid(),val,&val);cnt++;sleep(1);if(cnt==3)val=300;}}return 0; }
    • 效果:

    注:vfork() 保證子進程先運行,在它調(diào)用 exec(進程替換) 或 exit(退出進程)之后父進程才可能被調(diào)度運行;如果子進程沒有調(diào)用 exec, exit, 程序則會導致死鎖,程序是有問題的程序,沒有意義

    • 原生線程庫pthread:
  • 在Linux中,站在內(nèi)核角度沒有真正意義上線程相關(guān)的接口,但是站在用戶角度,當用戶想創(chuàng)建一個線程時更期望使用thread_create這樣類似的接口,因此系統(tǒng)為用戶層提供了原生線程庫pthread

  • 原生線程庫實際就是對輕量級進程的系統(tǒng)調(diào)用進行了封裝,在用戶層模擬實現(xiàn)了一套線程相關(guān)的接口

  • 3、線程優(yōu)缺點及其他分析

    • 線程的優(yōu)點:
  • 創(chuàng)建一個新線程的代價要比創(chuàng)建一個新進程小得多

  • 與進程之間的切換相比,線程之間的切換需要操作系統(tǒng)做的工作要少很多線程占用的資源要比進程少很多

  • 能充分利用多處理器的可并行數(shù)量

  • 在等待慢速I/O操作結(jié)束的同時,程序可執(zhí)行其他的計算任務(wù)

  • 計算密集型應(yīng)用,為了能在多處理器系統(tǒng)上運行,將計算分解到多個線程中實現(xiàn)

  • I/O密集型應(yīng)用,為了提高性能,將I/O操作重疊。線程可以同時等待不同的I/O操作(如邊下視頻邊看視頻)

    • 注意:
  • 計算密集型:執(zhí)行流的大部分任務(wù),主要以計算為主。比如加密解密、大數(shù)據(jù)查找等

  • IO密集型:執(zhí)行流的大部分任務(wù),主要以IO為主。比如刷磁盤、訪問數(shù)據(jù)庫、訪問網(wǎng)絡(luò)等

    • 線程的缺點:
  • 性能損失:一個很少被外部事件阻塞的計算密集型線程往往無法與共它線程共享同一個處理器。如果計算密集型線程的數(shù)量比可用的處理器多,那么可能會有較大的性能損失,這里的性能損失指的是增加了額外的同步和調(diào)度開銷,而可用的資源不變

  • 健壯性降低:編寫多線程需要更全面更深入的考慮,在一個多線程程序里,因時間分配上的細微偏差或者因共享了;不該共享的變量而造成不良影響的可能性是很大的,換句話說線程之間是缺保護的

  • 缺乏訪問控制:進程是訪問控制的基本粒度,在一個線程中調(diào)用某些OS函數(shù)會對整個進程造成影響

  • 編程難度提高:編寫與調(diào)試一個多線程程序比單線程程序困難得多

    • 線程異常:
  • 單個線程如果出現(xiàn)除零,野指針問題導致線程崩潰,進程也會隨著崩潰

  • 線程是進程的執(zhí)行分支,線程出異常,就類似進程出異常,進而觸發(fā)信號機制,終止進程,進程終止,該進程內(nèi)的所有線程也就隨即退出

    • 線程用途:
  • 合理的使用多線程,能提高CPU密集型程序的執(zhí)行效率

  • 合理的使用多線程,能提高IO密集型程序的用戶體驗(如生活中我們一邊寫代碼一邊下載開發(fā)工具,就是多線程運行的一種表現(xiàn))

  • 二、Linux進程VS線程

    1、進程和線程

    • 概念:
  • 進程是資源分配的基本單位

  • 線程是調(diào)度的基本單位

    • 線程共享進程數(shù)據(jù),但也有線程自己獨有的數(shù)據(jù):
  • 線程ID

  • 一組寄存器中線程自己的上下文數(shù)據(jù)

  • errno

  • 信號屏蔽字(handler方法是共享的)

  • 調(diào)度優(yōu)先級

    • 線程中共享的數(shù)據(jù):
  • 代碼段和數(shù)據(jù)段

  • 文件描述符表

  • 每種信號的處理方式

  • 當前工作目錄

  • 用戶id和組id

  • 注:進程的多個線程共享同一地址空間,因此Text Segment、Data Segment都是共享的,如果定義一個函數(shù),在各線程中都可以調(diào)用,如果定義一個全局變量,在各線程中都可以訪問到

    • 進程和線程的關(guān)系圖:

    三、Linux線程控制

    1、POSIX線程庫

    • pthread線程庫是應(yīng)用層的原生線程庫:
  • 應(yīng)用層指的是這個線程庫并不是系統(tǒng)接口直接提供的,而是由第三方提供的

  • 原生指的是大部分Linux系統(tǒng)都會默認帶上該線程庫

  • 與線程有關(guān)的函數(shù)構(gòu)成了一個完整的系列,絕大多數(shù)函數(shù)的名字都是以“pthread_”打頭的

  • 要使用這些函數(shù)庫,要通過引入頭文件<pthreaad.h>

  • 鏈接這些線程函數(shù)庫時,要使用編譯器命令的“-lpthread”選項

    • 錯誤檢查:
  • 傳統(tǒng)的一些函數(shù)是,成功返回0,失敗返回-1,并且對全局變量errno賦值以指示錯誤

  • pthreads函數(shù)出錯時不會設(shè)置全局變量errno(而大部分POSIX函數(shù)會這樣做),而是將錯誤代碼通過返回值返回

  • pthreads同樣也提供了線程內(nèi)的errno變量,以支持其他使用errno的代碼。對于pthreads函數(shù)的錯誤,建議通過返回值來判定,因為讀取返回值要比讀取線程內(nèi)的errno變量的開銷更小

  • 2、線程創(chuàng)建

    • pthread_create函數(shù)原型:
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
    • 解釋:
  • 功能:創(chuàng)建一個新的線程

  • 參數(shù):thread:輸出型參數(shù),返回獲取線程ID;attr:設(shè)置線程的屬性,attr為NULL表示使用默認屬性;start_routine:是個函數(shù)地址,線程啟動后要執(zhí)行的函數(shù),該函數(shù)返回值為void *,參數(shù)為void *;arg:傳給線程啟動函數(shù)的參數(shù)

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

    • 注意:
  • 主線程調(diào)用pthread_create函數(shù)創(chuàng)建一個新線程,此后新線程就會跑去執(zhí)行參入的函數(shù),而主線程則繼續(xù)往下執(zhí)行

  • 對于執(zhí)行函數(shù)來說,參數(shù)和返回值的類型都是void *,void *是一個通用的類型,可以傳入或者返回數(shù)據(jù)和其他類型的指針,從而傳入和帶出多樣的類型和數(shù)據(jù)

    • 示例:
    mypthread.c: #include<stdio.h> #include<unistd.h> #include<pthread.h> #include<stdlib.h> #include<string.h> int val=0; void* Routine(void* avgs) {while(1){printf("I am %s... val:%d\n",(char*)avgs,val);sleep(1);} } int main() {pthread_t tid1,tid2,tid3;int ret1=pthread_create(&tid1,NULL,Routine,(void*)"pthread 1");if(ret1!=0){fprintf(stderr,"pthread_creat:%s\n",strerror(ret1));exit(1);}int ret2=pthread_create(&tid2,NULL,Routine,(void*)"pthread 2");if(ret2!=0){fprintf(stderr,"pthread_creat:%s\n",strerror(ret2));exit(1);}int ret3=pthread_create(&tid3,NULL,Routine,(void*)"pthread 3");if(ret3!=0){fprintf(stderr,"pthread_creat:%s\n",strerror(ret3));exit(1);}while(1){printf("I am main pthread...val:%d\n",val++);sleep(1);}return 0; } Makefile: mypthread:mypthread.cgcc -o $@ $^ -pthread .PHONY:clean clean:rm -f mypthread
    • 效果:
    • 查看線程信息:ps -aL
    • 注意:
  • 默認情況下,ps命令不帶-L,看到的就是一個個的進程;帶-L就可以查看到每個進程內(nèi)的多個輕量級進程

  • 在Linux中,應(yīng)用層的線程與內(nèi)核的LWP是一一對應(yīng)的,實際上操作系統(tǒng)調(diào)度的時候采用的是LWP,而并非PID,只不過我們之前接觸到的都是單線程進程,其PID和LWP是相等的

  • 3、線程ID及線程地址空間布局

    • 概念:
  • pthread_ create函數(shù)會產(chǎn)生一個線程ID,存放在第一個參數(shù)指向的地址中。該線程ID和前面說的線程ID不是一回事
  • 前面講的線程ID(LWP)屬于進程調(diào)度的范疇。因為線程是輕量級進程,是操作系統(tǒng)調(diào)度器的最小單位,所以需要一個數(shù)值來唯一表示該線程
  • pthread_ create函數(shù)第一個參數(shù)指向一個虛擬內(nèi)存單元,該內(nèi)存單元的地址即為新創(chuàng)建線程的線程ID,屬于NPTL線程庫的范疇。線程庫的后續(xù)操作,就是根據(jù)該線程ID來操作線程的
  • 在Linux系統(tǒng)層面有LWP與線程對應(yīng),但是Linux是用輕量級進程模擬的線程,而對于用戶來說,并不會關(guān)心底層實現(xiàn),從用戶角度來說,他們也需要知道線程的信息,狀態(tài)以及操作線程,由此在共享區(qū)中還相應(yīng)的構(gòu)建了TCB(線程控制塊),便于用戶操作線程,在用戶區(qū)進行維護
    • pthread_ self函數(shù)原型:
    pthread_t pthread_self(void);

    功能:獲得線程自身的ID

    注:對于Linux目前實現(xiàn)的NPTL實現(xiàn)而言,pthread_t類型的線程ID,本質(zhì)就是一個進程地址空間上的一個地址

    • 示圖:

    注:主線程并不使用動態(tài)庫里的線程棧,而是使用進程里的棧

    4、線程終止

    • 終止線程的三種方法:
  • 從線程函數(shù)return

  • 線程可以調(diào)用pthread_ exit終止自己

  • 線程可以調(diào)用pthread_ cancel終止同一進程中的另一個線程或者自己

  • 注:在主線程使用return,以及在線程中使用exit都會終止整個進程

    • pthread_exit函數(shù)原型:
    void pthread_exit(void *value_ptr);
    • 解釋:
  • 功能:線程終止

  • 參數(shù):value_ptr線程退出傳出的數(shù)據(jù)(不要指向一個局部變量)

  • 返回值:無返回值,跟進程一樣,線程結(jié)束的時候無法返回到它自身

  • 注:pthread_exit或者return返回的指針所指向的內(nèi)存單元必須是全局的或者是用malloc分配的,不能在線程函數(shù)的棧上分配,因為當其它線程得到這個返回指針時線程函數(shù)已經(jīng)退出了

    • pthread_cancel函數(shù)原型:
    int pthread_cancel(pthread_t thread);
    • 解釋:
  • 功能:取消一個執(zhí)行中的線程

  • 參數(shù):thread表示要操作的線程的ID

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

  • 注:pthread_cancel函數(shù)具有一定的延時性,并不會立即被處理,不建議當線程立即被創(chuàng)建后立即進行cancel取消(線程創(chuàng)建,并不會立即被調(diào)度);也不建議在線程退出前執(zhí)行線程cancel取消(線程可能在取消之前就已經(jīng)退出了);建議在線程執(zhí)行中進行cancel取消線程

    示例:

    #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pthread.h> void *thread1(void *arg) {printf("%s returning ... \n",(char*)arg);int *p = (int*)malloc(sizeof(int));*p = 1;return (void*)p; } void *thread2(void *arg) {printf("%s exiting ...\n",(char*)arg);int *p = (int*)malloc(sizeof(int));*p = 2;pthread_exit((void*)p); } void *thread3(void *arg) {while ( 1 ){ //printf("%s is running ...\n",(char*)arg);sleep(1);} return NULL; } int main( void ) {pthread_t tid;void *ret;// thread 1 returnpthread_create(&tid, NULL, thread1, (void*)"thread 1");pthread_join(tid, &ret);printf("thread 1 return, thread id %X, return code:%d\n", tid, *(int*)ret);free(ret);// thread 2 exitpthread_create(&tid, NULL, thread2, (void*)"thread 2");pthread_join(tid, &ret);printf("thread 2 exit, thread id %X, return code:%d\n", tid, *(int*)ret);free(ret);// thread 3 cancel by otherpthread_create(&tid, NULL, thread3, (void*)"thread 3");sleep(3);pthread_cancel(tid);pthread_join(tid, &ret);if (ret == PTHREAD_CANCELED)printf("thread 3 cancel, thread id %X, return code: PTHREAD_CANCELED->%d\n", tid,ret);elseprintf("thread return, thread id %X, return code:%d\n", tid,ret);return 0; }
    • 效果:

    5、線程等待

    • 為什么需要線程等待:
  • 已經(jīng)退出的線程,其空間沒有被釋放,仍然在進程的地址空間內(nèi),創(chuàng)建新的線程不會復用剛才退出線程的地址空間,如果主線程不對新線程進行等待,那么這個新線程的資源也是不會被回收的。如果不等待會產(chǎn)生內(nèi)存泄漏

  • 線程是用來執(zhí)行分配的任務(wù)的,如果主線程想知道任務(wù)完成的怎么樣,那么就有必要對線程進行等待,獲取線程退出的信息

    • pthread_join函數(shù)原型:
    int pthread_join(pthread_t thread, void **value_ptr);
    • 解釋:
  • 功能:等待線程結(jié)束

  • 參數(shù):thread:指定等待線程的ID;value_ptr:輸出型參數(shù),用來獲取指向線程的返回值

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

    • 注意:
  • 調(diào)用該函數(shù)的線程將掛起等待,直到id為thread的線程終止

  • 這里獲取的線程退出信息并沒有終止信號信息,而終止信號信息是對于整個進程來說的,如果線程收到信號崩潰也會導致整個進程也崩潰

  • thread線程以不同的方法終止,通過pthread_join得到的終止狀態(tài)是不同的

    • 終止獲取的狀態(tài)情況:
  • 如果thread線程通過return返回,value_ ptr所指向的單元里存放的是thread線程函數(shù)的返回值

  • 如果thread線程被別的線程調(diào)用pthread_ cancel異常終掉,value_ ptr所指向的單元里存放的是常數(shù)PTHREAD_ CANCELED

  • 如果thread線程是自己調(diào)用pthread_exit終止的,value_ptr所指向的單元存放的是傳給pthread_exit的參數(shù)

  • 如果對thread線程的終止狀態(tài)不感興趣,可以傳NULL給value_ ptr參數(shù)

    • 示圖:
    • 示例:
    #include<stdio.h> #include<unistd.h> #include<pthread.h> #include<stdlib.h> #include<string.h> int val=0; struct Ret {int exitno;int exittime;//... }; void* Routine(void* avgs) {int cnt=1;while(1){printf("I am %s... val:%d\n",(char*)avgs,val);sleep(1);cnt++;if(cnt==3){struct Ret* p=(struct Ret*)malloc(sizeof(struct Ret));p->exitno=0;p->exittime=6666;pthread_exit((void*)p);//pthread_cancel(pthread_self());} }} int main() {pthread_t tid1,tid2,tid3;pthread_create(&tid1,NULL,Routine,(void*)"pthread 1");pthread_create(&tid2,NULL,Routine,(void*)"pthread 2");pthread_create(&tid3,NULL,Routine,(void*)"pthread 3");int cnt=0;while(1){printf("I am main pthread...val:%d\n",val++);sleep(1);cnt++;if(cnt==3)break;}printf("wait for pthread...\n");void* ret;pthread_join(tid1,&ret);printf("pthread id:%x exitno:%d exittime:%d\n",tid1,((struct Ret*)ret)->exitno,((struct Ret*)ret)->exittime);pthread_join(tid2,&ret);printf("pthread id:%x exitno:%d exittime:%d\n",tid2,((struct Ret*)ret)->exitno,((struct Ret*)ret)->exittime);pthread_join(tid3,&ret);printf("pthread id:%x exitno:%d exittime:%d\n",tid3,((struct Ret*)ret)->exitno,((struct Ret*)ret)->exittime);return 0; }
    • 效果:

    6、線程分離

    • 概念:
  • 默認情況下,新創(chuàng)建的線程是joinable的,線程退出后,需要對其進行pthread_join操作,否則無法釋放資源,從而造成系統(tǒng)泄漏

  • 如果不關(guān)心線程的返回值,join是一種負擔,這個時候,我們可以告訴系統(tǒng),當線程退出時,自動釋放線程資源

    • pthread_detach函數(shù)原型:
    int pthread_detach(pthread_t thread);
    • 注意:
  • 可以是線程組內(nèi)其他線程對目標線程進行分離,也可以是線程自己分離: pthread_detach(pthread_self());
  • joinable和分離是沖突的,一個線程不能既是joinable又是分離的
  • 線程的分離也是具有一定延時性,分離之后如果再進行等待那么得到返回的結(jié)果是未定義的
  • 線程分離后只是回收的時候自動進行回收,如果主線程先退出,那么整個進程也會退出;如果分離的線程執(zhí)行崩潰,同樣的整個進行也會崩潰
    • 示例:
    #include <stdio.h> #include <pthread.h> #include <unistd.h> void* Routine (void* arg) {pthread_detach(pthread_self());printf("%s detach success!\n");int cnt=0;while(cnt<5){cnt++;printf("%s running...\n",(char*)arg);sleep(1);}printf("%s return...\n");return NULL; } int main() {pthread_t tid;pthread_create(&tid,NULL,Routine,(void*)"thread");sleep(2);//等待線程分離void* ret;if(pthread_join(tid,&ret)==0)printf("thread join success! ret:%d\n",(int*)ret);else printf("thread join fail... ret:%d\n",(int*)ret);return 0; }
    • 效果:

    總結(jié)

    以上是生活随笔為你收集整理的Linux线程-概念和控制的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。