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

歡迎訪問 生活随笔!

生活随笔

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

linux

Linux线程详解

發布時間:2023/12/31 linux 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Linux线程详解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

并行和并發的區別

1. 并發(concurrency):在操作系統中,是指一個時間段中有幾個程序都處于已啟動運行到運行完畢之間,且這幾個程序都是在同一個處理機上運行。其中兩種并發關系分別是同步互斥。(并發是指同一時刻只能有一條指令執行,但多個進程指令被快速輪換執行,使得在宏觀上有多個進程被同時執行的效果--宏觀上并行,針對單核處理器)

  • 互斥:進程間相互排斥的使用臨界資源的現象,就叫互斥。
  • 同步(synchronous):進程之間的關系不是相互排斥臨界資源的關系,而是相互依賴的關系。進一步的說明:就是前一個進程的輸出作為后一個進程的輸入,當第一個進程沒有輸出時第二個進程必須等待。具有同步關系的一組并發進程相互發送的信息稱為消息或事件。(彼此有依賴關系的調用不應該同時發生,而同步就是阻止那些“同時發生”的事情
  • 其中并發又有偽并發和真并發,偽并發是指單核處理器的并發,真并發是指多核處理器的并發。

2.并行(parallelism):在單處理器中多道程序設計系統中,進程被交替執行,表現出一種并發的外部特種;在多處理器系統中,進程不僅可以交替執行,而且可以重疊執行。在多處理器上的程序才可實現并行處理。從而可知,并行是針對多處理器而言的。并行是同時發生的多個并發事件,具有并發的含義,但并發不一定并行,也亦是說并發事件之間不一定要同一時刻發生。(同一時刻,有多條指令在多個處理器上同時執行--針對多核處理器

同步和異步的區別

  • 同步(synchronous):進程之間的關系不是相互排斥臨界資源的關系,而是相互依賴的關系。進一步的說明:就是前一個進程的輸出作為后一個進程的輸入,當第一個進程沒有輸出時第二個進程必須等待。具有同步關系的一組并發進程相互發送的信息稱為消息或事件。
  • 異步(asynchronous):異步和同步是相對的,同步就是順序執行,執行完一個再執行下一個,需要等待、協調運行。異步就是彼此獨立,在等待某事件的過程中繼續做自己的事,不需要等待這一事件完成后再工作。線程就是實現異步的一個方式。異步是讓調用方法的主線程不需要同步等待另一線程的完成,從而可以讓主線程干其它的事情。

線程概念

什么是線程

  • LWP:light weight process 輕量級的進程,本質仍是進程(在Linux環境下)
  • 進程:獨立地址空間,擁有PCB
  • 線程:也有PCB,但沒有獨立的地址空間(共享)
  • 區別:在于是否共享地址空間。 獨居(進程);合租(線程)。
  • Linux下: 線程:最小的執行單位,調度的基本單位。
  • 進程:最小分配資源單位,可看成是只有一個線程的進程。

Linux內核線程實現原理

類Unix系統中,早期是沒有“線程”概念的,80年代才引入,借助進程機制實現出了線程的概念。因此在這類系統中,進程和線程關系密切。

  • 輕量級進程(light-weight process),也有PCB,創建線程使用的底層函數和進程一樣,都是clone。
  • 從內核里看進程和線程是一樣的,都有各自不同的PCB,但是PCB中指向內存資源的三級頁表是相同的。
  • 進程可以蛻變成線程
  • 線程可看做寄存器和棧的集合
  • 在linux下,線程最是小的執行單位;進程是最小的分配資源單位
  • 察看LWP號:ps?–Lf pid 查看指定線程的lwp號。

三級映射:進程PCB --> 頁目錄(可看成數組,首地址位于PCB中) --> 頁表 --> 物理頁面 --> 內存單元--參考:《Linux內核源代碼情景分析》 ----毛德操

  • 對于進程來說,相同的地址(同一個虛擬地址)在不同的進程中,反復使用而不沖突。原因是他們雖虛擬址一樣,但,頁目錄、頁表、物理頁面各不相同。相同的虛擬址,映射到不同的物理頁面內存單元,最終訪問不同的物理頁面。
  • 但!線程不同!兩個線程具有各自獨立的PCB,但共享同一個頁目錄,也就共享同一個頁表和物理頁面。所以兩個PCB共享一個地址空間。
  • 實際上,無論是創建進程的fork,還是創建線程的pthread_create,底層實現都是調用同一個內核函數clone。
  • 如果復制對方的地址空間,那么就產出一個“進程”;如果共享對方的地址空間,就產生一個“線程”。
  • 因此:Linux內核是不區分進程和線程的。只在用戶層面上進行區分。所以,線程所有操作函數 pthread_* 是庫函數,而非系統調用。

線程共享資源

  • 1.文件描述符表
  • 2.每種信號的處理方式
  • 3.當前工作目錄
  • 4.用戶ID和組ID
  • 5.內存地址空間 (.text/.data/.bss/heap/共享庫)

線程非共享資源

  • 1.線程id
  • 2.處理器現場和棧指針(內核棧)
  • 3.獨立的??臻g(用戶空間棧)
  • 4.errno變量
  • 5.信號屏蔽字
  • 6.調度優先級

線程優、缺點

  • 優點: 1. 提高程序并發性 2. 開銷小 3. 數據通信、共享數據方便
  • 缺點: 1. 庫函數,不穩定 2. 調試、編寫困難、gdb不支持 3. 對信號支持不好
  • 優點相對突出,缺點均不是硬傷。Linux下由于實現方法導致進程、線程差別不是很大。

線程控制原語

pthread_self函數? 獲取線程ID。其作用對應進程中 getpid() 函數。

  • pthread_t pthread_self(void); 返回值:成功:0; 失敗:無!
  • 線程ID:pthread_t類型,本質:在Linux下為無符號整數(%lu),其他系統中可能是結構體實現
  • 線程ID是進程內部,識別標志。(兩個進程間,線程ID允許相同)
  • 注意:不應使用全局變量 pthread_t tid,在子線程中通過pthread_create傳出參數來獲取線程ID,而應使用pthread_self。

pthread_create函數? 創建一個新線程。 其作用,對應進程中fork() 函數。

  • int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
  • 返回值:成功:0; 失敗:錯誤號 -----Linux環境下,所有線程特點,失敗均直接返回錯誤號。

參數:

  • pthread_t:當前Linux中可理解為:typedef ?unsigned long int ?pthread_t;
  • 參數1:傳出參數,保存系統為我們分配好的線程ID
  • 參數2:通常傳NULL,表示使用線程默認屬性。若想使用具體屬性也可以修改該參數。
  • 參數3:函數指針,指向線程主函數(線程體),該函數運行結束,則線程結束。
  • 參數4:線程主函數執行期間所使用的參數,如要傳多個參數, 可以用結構封裝。
#include <stdio.h> #include <pthread.h> #include <unistd.h>void *fun(void *arg) {printf("I'm thread, Thread ID = %lu\n", pthread_self());return NULL; }int main(void) {pthread_t tid;pthread_create(&tid, NULL, fun, NULL);sleep(1); // 在多線程環境中,父線程終止,全部子線程被迫終止printf("I am main, my pid = %d\n", getpid());return 0; }

運行結果

  • 在一個線程中調用pthread_create()創建新的線程后,當前線程從pthread_create()返回繼續往下執行,而新的線程所執行的代碼由我們傳給pthread_create的函數指針start_routine決定。start_routine函數接收一個參數,是通過pthread_create的arg參數傳遞給它的,該參數的類型為void *,這個指針按什么類型解釋由調用者自己定義。start_routine的返回值類型也是void *,這個指針的含義同樣由調用者自己定義。start_routine返回時,這個線程就退出了,其它線程可以調用pthread_join得到start_routine的返回值,類似于父進程調用wait(2)得到子進程的退出狀態,稍后詳細介紹pthread_join。
  • pthread_create成功返回后,新創建的線程的id被填寫到thread參數所指向的內存單元。我們知道進程id的類型是pid_t,每個進程的id在整個系統中是唯一的,調用getpid(2)可以獲得當前進程的id,是一個正整數值。線程id的類型是thread_t,它只在當前進程中保證是唯一的,在不同的系統中thread_t這個類型有不同的實現,它可能是一個整數值,也可能是一個結構體,也可能是一個地址,所以不能簡單地當成整數用printf打印,調用pthread_self(3)可以獲得當前線程的id。

練習:循環創建多個線程,每個線程打印自己是第幾個被創建的線程。(類似于進程循環創建子進程)

#include <pthread.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h>void *tfn(void *arg) {int i;i = (int)arg;//i = *((int *)arg);sleep(i); //通過i來區別每個線程printf("I'm %dth thread, Thread_ID = %lu\n", i+1, pthread_self());return NULL; }int main(int argc, char *argv[]) {int n, i;pthread_t tid;if (argc == 2)n = atoi(argv[1]);for (i = 0; i < n; i++) {pthread_create(&tid, NULL, tfn, (void *)i); //將i轉換為指針,在tfn中再強轉回整形。}sleep(n);printf("I am main, and I am not a process, I'm a thread!\n" "main_thread_ID = %lu\n", pthread_self());return 0; }

運行結果

線程與共享? 線程間共享全局變量

  • 【牢記】:線程默認共享數據段、代碼段等地址空間,常用的是全局變量。而進程不共享全局變量,只能借助mmap。

設計程序,驗證線程之間共享全局數據。

#include <stdio.h> #include <pthread.h> #include <stdlib.h> #include <unistd.h>int var = 100;void *tfn(void *arg) {var = 200;printf("thread\n");return NULL; }int main(void) {printf("At first var = %d\n", var);pthread_t tid;pthread_create(&tid, NULL, tfn, NULL);sleep(1);printf("after pthread_create, var = %d\n", var);return 0; }

運行結果

pthread_exit函數? 將單個線程退出

  • void pthread_exit(void *retval); 參數:retval表示線程退出狀態,通常傳NULL
#include <pthread.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h>void *tfn(void *arg) {int i;i = (int)arg; //強轉。if (i == 2)pthread_exit(NULL);sleep(i); //通過i來區別每個線程printf("I'm %dth thread, Thread_ID = %lu\n", i+1, pthread_self());return NULL; }int main(int argc, char *argv[]) {int n , i;pthread_t tid;if (argc == 2)n = atoi(argv[1]);for (i = 0; i < n; i++){pthread_create(&tid, NULL, tfn, (void *)i); //將i轉換為指針,在tfn中再強轉回整形。}sleep(n);printf("I am main, I'm a thread!\n" "main_thread_ID = %lu\n", pthread_self());return 0; }

運行結果

思考:使用exit將指定線程退出,可以嗎??結論:線程中,禁止使用exit函數,會導致進程內所有線程全部退出。

  • 在不添加sleep控制輸出順序的情況下。pthread_create在循環中,幾乎瞬間創建5個線程,但只有第1個線程有機會輸出(或者第2個也有,也可能沒有,取決于內核調度)如果第3個線程執行了exit,將整個進程退出了,所以全部線程退出了。
  • 所以,多線程環境中,應盡量少用,或者不使用exit函數,取而代之使用pthread_exit函數,將單個線程退出。任何線程里exit導致進程退出,其他線程未工作結束,主控線程退出時不能return或exit。
  • 另注意,pthread_exit或者return返回的指針所指向的內存單元必須是全局的或者是用malloc分配的,不能在線程函數的棧上分配,因為當其它線程得到這個返回指針時線程函數已經退出了。

pthread_join函數? 阻塞等待線程退出,獲取線程退出狀態 其作用,對應進程中 waitpid() 函數。

  • int pthread_join(pthread_t thread, void **retval); 成功:0;失敗:錯誤號
  • 參數:thread:線程ID (【注意】:不是指針);retval:存儲線程結束狀態。
#include <stdio.h> #include <unistd.h> #include <pthread.h> #include <stdlib.h>typedef struct {int a;int b; } exit_t;void *tfn(void *arg) {exit_t *ret;ret = malloc(sizeof(exit_t)); // malloc分配ret->a = 100;ret->b = 300;sleep(3);//pthread_exit((void *)ret);return (void *)ret; }int main(void) {pthread_t tid;exit_t *retval;pthread_create(&tid, NULL, tfn, NULL);/*調用pthread_join可以獲取線程的退出狀態*/pthread_join(tid, (void **)&retval); //wait(&status);printf("a = %d, b = %d \n", retval->a, retval->b);return 0; }

對比記憶:

  • 進程中:main返回值、exit參數-->int;等待子進程結束 wait 函數參數-->int *
  • 線程中:線程主函數返回值、pthread_exit-->void *;等待線程結束 pthread_join 函數參數-->void **
  • 參數 retval 非空用法

    調用該函數的線程將掛起等待,直到id為thread的線程終止。thread線程以不同的方法終止,通過pthread_join得到的終止狀態是不同的,總結如下:

  • 如果thread線程通過return返回,retval所指向的單元里存放的是thread線程函數的返回值。
  • 如果thread線程被別的線程調用pthread_cancel異常終止掉,retval所指向的單元里存放的是常數PTHREAD_CANCELED。
  • 如果thread線程是自己調用pthread_exit終止的,retval所指向的單元存放的是傳給pthread_exit的參數。
  • 如果對thread線程的終止狀態不感興趣,可以傳NULL給retval參數。
  • 練習:使用pthread_join函數將循環創建的多個子線程回收。

    #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <pthread.h>int var = 100;void *tfn(void *arg) {int i;i = (int)arg;sleep(i);if (i == 1) {var = 333;printf("var = %d\n", var);return (void *)var;}else if (i == 3) {var = 777;printf("I'm %dth pthread, pthread_id = %lu\n var = %d\n", i+1, pthread_self(), var);pthread_exit((void *)var);} else {printf("I'm %dth pthread, pthread_id = %lu\n var = %d\n", i+1, pthread_self(), var);pthread_exit((void *)var);}return NULL; }int main(void) {pthread_t tid[5];int i;int *ret[5]; for (i = 0; i < 5; i++)pthread_create(&tid[i], NULL, tfn, (void *)i);for (i = 0; i < 5; i++) {pthread_join(tid[i], (void **)&ret[i]);printf("-------%d 's ret = %d\n", i, (int)ret[i]);}printf("I'm main pthread tid = %lu\t var = %d\n", pthread_self(), var);sleep(i);return 0; }

    運行結果

    pthread_cancel函數? 殺死(取消)線程 其作用,對應進程中 kill() 函數。

    • int pthread_cancel(pthread_t thread); 成功:0;失敗:錯誤號
    #include <stdio.h> #include <unistd.h> #include <pthread.h> #include <stdlib.h>void *tfn1(void *arg) {printf("thread 1 returning\n");return (void *)111; }void *tfn2(void *arg) {printf("thread 2 exiting\n");pthread_exit((void *)222); }void *tfn3(void *arg) {while (1) {printf("thread 3: I'm going to die in 3 seconds ...\n");sleep(1);pthread_testcancel(); //自己添加取消點*/}return (void *)666; }int main(void) {pthread_t tid;void *tret = NULL;pthread_create(&tid, NULL, tfn1, NULL);pthread_join(tid, &tret);printf("thread 1 exit code = %d\n\n", (int)tret);pthread_create(&tid, NULL, tfn2, NULL); pthread_join(tid, &tret);printf("thread 2 exit code = %d\n\n", (int)tret);pthread_create(&tid, NULL, tfn3, NULL);sleep(3);pthread_cancel(tid);pthread_join(tid, &tret);printf("thread 3 exit code = %d\n", (int)tret);return 0; }

    運行結果

    • 線程的取消并不是實時的,而有一定的延時。需要等待線程到達某個取消點(檢查點)。
    • 類似于玩游戲存檔,必須到達指定的場所(存檔點,如:客棧、倉庫、城里等)才能存儲進度。殺死線程也不是立刻就能完成,必須要到達取消點。
    • 取消點:是線程檢查是否被取消,并按請求進行動作的一個位置。通常是一些系統調用creat,open,pause,close,read,write.....?執行命令man?7 pthreads可以查看具備這些取消點的系統調用列表。也可參閱 APUE.12.7 取消選項小節。
    • 可粗略認為一個系統調用(進入內核)即為一個取消點。如線程中沒有取消點,可以通過調用pthreestcancel函數自行設置一個取消點。
    • 被取消的線程, 退出值定義在Linux的pthread庫中。常數PTHREAD_CANCELED的值是-1??稍陬^文件pthread.h中找到它的定義:#define PTHREAD_CANCELED ((void *) -1)。因此當我們對一個已經被取消的線程使用pthread_join回收時,得到的返回值為-1。

    pthread_detach函數? 實現線程分離

    • int pthread_detach(pthread_t thread); 成功:0;失敗:錯誤號
    • 線程分離狀態:指定該狀態,線程主動與主控線程斷開關系。線程結束后,其退出狀態不由其他線程獲取,而直接自己自動釋放。網絡、多線程服務器常用。
    • 在線程被分離后,不能使用pthread_join等待它的終止狀態。
    • 進程若有該機制,將不會產生僵尸進程。僵尸進程的產生主要由于進程死后,大部分資源被釋放,一點殘留資源仍存于系統中,導致內核認為該進程仍存在。
    • 也可使用 pthread_create函數參2(線程屬性)來設置線程分離。

    使用pthread_detach函數實現線程分離?

    #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <pthread.h>void *tfn(void *arg) {int n = 3;while (n--) {printf("thread count %d\n", n);sleep(1); }return (void *)1;//pthread_exit((void *)1); }int main(void) {pthread_t tid;void *tret;int err;#if 1pthread_attr_t attr; /*通過線程屬性來設置游離態*/pthread_attr_init(&attr);pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);pthread_create(&tid, &attr, tfn, NULL);#elsepthread_create(&tid, NULL, tfn, NULL);pthread_detach(tid); //讓線程分離 ----自動退出,無系統殘留資源#endifwhile (1) {err = pthread_join(tid, &tret);printf("thread exit code = %d\n", (int)tret);printf("-------------err= %d\n", err);if (err != 0)fprintf(stderr, "thread_join error: %s\n\n", strerror(err));elsefprintf(stderr, "thread exit code %d\n\n", (int)tret);sleep(1);}return 0; }

    運行結果

    • 一般情況下,線程終止后,其終止狀態一直保留到其它線程調用pthread_join獲取它的狀態為止。但是線程也可以被置為detach狀態,這樣的線程一旦終止就立刻回收它占用的所有資源,而不保留終止狀態。不能對一個已經處于detach狀態的線程調用pthread_join,這樣的調用將返回EINVAL錯誤。也就是說,如果已經對一個線程調用了pthread_detach就不能再調用pthread_join了。

    終止線程方式

    總結:終止某個線程而不終止整個進程,有三種方法:

  • 從線程主函數return。這種方法對主控線程不適用,從main函數return相當于調用exit。
  • 一個線程可以調用pthread_cancel終止同一進程中的另一個線程。
  • 線程可以調用pthread_exit終止自己。
  • 控制原語對比

    進程? ? ? ? ? ? 線程

    fork? ? ? ? ? ? ?pthread_create

    exit? ? ? ? ? ? ?pthread_exit

    wait? ? ? ? ? ? pthread_join

    kill? ? ? ? ? ? ? pthread_cancel

    getpid? ? ? ? pthread_self 命名空間

    ?

    總結

    以上是生活随笔為你收集整理的Linux线程详解的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。