前言
? ? ? ?
? ? ? ? 以<深入理解計算機系統>(以下稱“本書”)內容為基礎,對程序的整個過程進行梳理。本書內容對整個計算機系統做了系統性導引,每部分內容都是單獨的一門課.學習深度根據自己需要來定
引入
? ? ? ? 接續上一篇理解計算機系統_并發編程(4)_基于線程的并發(一):線程基礎-CSDN博客
Posix線程簡介
? ? ? ? 概念:Posix線程是在C程序中處理線程的一個標準接口.而且在所有Linux系統上都可用.Pthreads定義了大約60個函數,允許程序創建,殺死和回收線程,與對等線程安全地共享數據,還可以通知對等線程系統狀態地變化.
? ? ? ? ---解讀:有api可以用是讓人興奮的,代碼要寫起來.
一個簡單的Pthreads程序
? ? ? ? 主函數(代表主線程)創建一個對等線程,然后等待他的終止.當主線程檢測到對等線程終止后,他就通過調用exit終止該進程.
hello.c內容
#include"csapp.h"
void *thread(void *vargp);int main(){pthread_t tid;Pthread_create(&tid,NULL,thread,NULL); //生成對等線程Pthread_join(tid,NULL); //等待對等線程終止exit(0); //終止當前進程中所有線程
}void *thread(void *vargp){printf("Hello,world!\n");return NULL;
}
? ? ? ? ---解讀:從示例代碼可以看出:線程和任務是分離的.主程序只管線程的生成和終止.任務由函數thread給出.二者之間的聯系在Pthread_create()函數里,兩個參數分別傳入了函數名thread和代表參數vargp的NULL.---"分離"是構建程序時常用的方法.他的優點是便于模塊化代碼,也便于分工.
任務函數的分析
? ? ? ? 本書原話:線程的代碼和本地數據被封裝在一個線程例程(thread routine)中.正如第二行里的原型所示,每個線程例程都以一個通用指針作為輸入,并返回一個通用指針.如果想傳遞多個參數給線程例程,那么你應該將參數放到一個結構中,并傳遞一個指向該結構的指針.相似地,如果想要線程例程返回多個參數,你可以返回一個指向一個結構的指針. ---斜體字部分有解釋
? ? ? ? ---解讀:通用指針可以表示的數據,理論上是無限的.他可以指向一個結構(或結構數組),結構里面再擴展數據.
? ? ? ? 這里有一個疑問:"線程例程返回多個參數,可以返回一個指向一個結構的指針",問題是線程例程的返回值在哪里?主函數中并沒有接收.
????????解決方案:輸入參數和返回值放進一個結構(vargp)里.? 舉個例子,聲明一個結構表示結果和參數
typedef struct{struct result* res;struct argument* arg;
}res_arg;
? ? ? ? 給Pthread_create傳第4個參數時傳入struct res_arg*類型的數據.形參和返回值的數據分別由struct result和struct argument定義.
? ? ? ? 這種寫法的思路是用形參指針接收函數返回值.筆者以前寫過:理解計算機系統_網絡編程(4)_套接字api-CSDN博客
線程API? ? ? ??
創建線程pthread_create()?
? ? ? ? 函數原型如下
typedef void *(func)(void *);int pthread_create(pthread_t *tid,pthread_attr_t *attr,func *f, void *arg); //成功返回值為0,出錯為非0
? ? ? ? ?---解讀:函數等于任務,用函數指針func表示,然后把函數指針和參數指針傳入pthread_create()函數的第3個和第4個形參中,表示函數被放入線程tid中運行.
? ? ? ? 第1個參數tid理解為生成一個線程ID.結合主函數來看:定義一個pthread_t變量,傳入地址給函數.當pthread_create()返回時,參數tid包含新創建線程的ID.---黑體字是原話,佐證
? ? ? ? 第2個參數attr改變新創建線程的默認屬性.本書認為改變這些屬性超出學習范圍,調用時總傳入NULL
獲得自己的線程ID函數pthread_self()
? ? ? ? 函數原型
pthread_t pthread_self(void);
//返回調用者的線程ID
? ? ? ? 顯然獲得自己的線程必須在生成線程號之后---必須先調用pthread_create()
終止線程
? ? ? ? 一個線程的終止的方式:
? ? ? ? 1.當頂層的線程例程返回時,線程會隱式地終止. ---任務執行后所在線程自動終止,不需要干預
? ? ? ? 2.通過調用pthread_exit函數,線程會顯式地終止.函數原型如下:
void pthread_exit(void *thread_return);
//不返回
? ? ? ? 3.某個對等線程調用Linux的exit函數,該函數終止進程以及所有與該進程相關的線程.
? ? ? ? 4.另一個對等線程通過以當前ID作為參數調用pthread_cancel函數來終止當前進程.
? ? ? ? ---解讀:筆者認為以上方法沒有多少應用場景,
????????比如第4條,讓一個線程終止另一個線程,意義何在?第3條調用exit函數,相當于強制退出所有線程和進程.第2條由主線程調用后等待并終止,和示例程序中主線程調用exit的意思是相同的,多了一個thread_return可以設置一些數據,而這個工作可以留到"回收已終止線程的資源"里去做.
? ? ? ? 綜上所述,暫時不用多看,用示例里的寫法即可.如果有遇到相應場景再來查.
回收已終止線程的資源pthread_join()函數
? ? ? ? 函數原型
int pthread_join(pthread_t tid,void **thread_return);
//成功返回0,出錯則為非0
? ? ? ? ---解讀:函數會阻塞,直到線程終止,回收已終止線程的資源.有兩點說明
? ? ? ? 1>必須指定線程ID,這點和進程中的wait函數不一樣,先執行完的子進程調用wait? ?
? ? ? ? ?2>將線程例程返回的通用(void *)指針賦值為thread_return指向的位置.這里解決了上面標題"任務函數的分析"提出的疑問:線程任務運行的返回值去了哪里?線程返回的結構指針,被另一個雙重指針接收.
分離線程pthread_detach()函數
? ? ? ? 線程分離detach和線程結合join是相對的.
? ? ? ? ?本書原話:一個可結合的線程能夠被其他線程收回和殺死.在被其他線程回收之前,他的內存資源(例如棧)是不釋放的.相反,一個分離的線程是不能被其他線程回收或殺死,他的內存資源在他終止時由系統自動釋放.
? ? ? ? 函數原型
int pthread_detach(pthread_t tid);
//若成功返回0,出錯返回非0
? ? ? ? ?同時,線程能夠以pthread_self()為參數調用來分離自己.
? ? ? ? ---解讀:線程資源面臨釋放問題,join和detach二選一.兩者對比如下:
????????join顯式釋放資源,并接收線程任務返回值(也是顯式).
????????detach自動釋放資源,暫時沒看見接收線程任務返回值的地方(如果需要接收,可以把輸入參數和返回值放進一個結構(vargp)里),寫法上detach相對簡單一點(見本書P695"基于線程的并發服務器"代碼)
初始化線程pthread_once()函數(略)
基于線程的并發echo服務器? ?
? ? ? ?本書P695中間echoservert.c,這里代碼在之前基礎上稍作了修改.還是不難看懂,
? ? ? ? 注意:
? ? ? ? 1>第21行代碼可以和第8行合并,寫到第8行
int *connfdp=Malloc(sizeof(int));
? ? ? ? 2>第22行,第23行和第30行結合看,把已連接描述符connfd(int類型)先作為指針傳入pthread_create,然后在thread函數中,先強轉為int型指針,再提取出描述符的寫法.
? ? ? ? 3>第31行線程資源回收調用了detach()函數,在任務中先分離自己,自動回收資源
? ? ? ? 4>后面講了出錯的原因是"競爭"
小結
? ? ? ? 線程api的一點理解
????????