Linux Posix Thread
在一個程序裏的一個執行路線(routine)就叫做線程(thread)。更準確的定義是:線程是“一個進程內部的控制序列”。一切進程至少都有一個執行線程。
進程與線程
進程是資源競爭的基本單位
線程是程序執行的最小單位
線程共享進程數據,但也擁有自己的一部分數據
線程ID
程序計數器 PC指針
寄存器組
數據棧
errno
一個進程內部的線程可以共享資源
代碼段
數據段
打開文件和信號
程序 放在 磁盤 靜態數據,一堆指令的集合,數據+指令組成
進程 程序的動態執行過程,相當於CPU的調度,數據段,堆棧段,代碼段 PCB控制塊
linux通過PCB來控制進程
線程 linux 執行的最小 單元,線程體就是一個函數調用。
fork和創建新線程的區別
當一個進程執行一個fork調用的時候,會創建出進程的一個新拷貝,新進程將擁有它自己的變量和它自己的PID。這個新進程的運行時間是獨立的,它在執行時幾乎完全獨立於創建它的進程。在進程裏面創建一個新線程的時候,新的執行線程會擁有自己的堆棧(因此也就有自己的局部變量),但要與它的創建者共享全局變量、文件描述符、信號處理器和當前的工作目錄狀態
線程的優點
創建一個新線程的代價要比創建一個新進程小得多
與進程之間的切換相比,線程之間的切換需要操作系統做的工作要少很多
線程占用的資源要比進程少很多
能充分利用多處理器的可並行數量
在等待慢速I/O操作結束的同時,程序可執行其他的計算任務
計算密集型應用,為了能在多處理器系統上運行,將計算分解到多個線程中實現
I/O密集型應用,為了提高性能,將I/O操作重疊。線程可以同時等待不同的I/O操作。
線程缺點
性能損失
一個很少被外部事件阻塞的計算密集型線程往往無法與共它線程共享同一個處理器。如果計算密集型線程的數量比可用的處理器多,那麽可能會有較大的性能損失,這裏的性能損失指的是增加了額外的同步和調度開銷,而可用的資源不變。
健壯性降低
編寫多線程需要更全面更深入的考慮,在一個多線程程序裏,因時間分配上的細微偏差或者因共享了不該共享的變量而造成不良影響的可能性是很大的,換句話說線程之間是缺乏保護的。
缺乏訪問控制
進程是訪問控制的基本粒度,在一個線程中調用某些OS函數會對整個進程造成影響。
編程難度提高
編寫與調試一個多線程程序比單線程程序困難得多
線程調度競爭範圍
操作系統提供了各種模型,用來調度應用程序創建的線程。這些模型之間的主要不同是:在競爭系統資源(特別是CPU時間)時,線程調度競爭範圍(thread-scheduling contention scope)不一樣。
進程競爭範圍(process contention scope):各個線程在同一進程競爭“被調度的CPU時間”(但不直接和其他進程中的線程競爭)。
系統競爭範圍(system contention scope):線程直接和“系統範圍”內的其他線程競爭。
線程的屬性是可以修改的,默認情況下是系統競爭範圍。
線程調用API熟悉
pthread_create函數創建一個新的線程
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg);
thread:返回線程ID
attr:設置線程的屬性,attr為NULL表示使用默認屬性
start_routine:是個函數地址,線程啟動後要執行的函數,線程的執行體
arg:傳給線程啟動函數的參數
成功返回0;失敗返回錯誤碼
線程執行完函數調用就消失了,不會在執行pthread_create下面的代碼,這個是和進程的區別。一般請款下,進程和線程是並行運行的。線程是依賴於進程之上,如果進程死了,線程也要死。進程不一樣,父進程死了,子進程也可以自己運行,因為子進程有自己的獨立的內存空間。線程依賴於進程的生命周期,這一點和子進程是不同的,
pthread_self函數返回線程ID
pthread_t pthread_self(void);
返回值:總是成功返回調用者的TID
pthread_join函數等待子線程結束
int pthread_join(pthread_t thread, void **value_ptr);
thread:線程ID
value_ptr:它指向一個指針,後者指向線程的返回值 會送給父進程的運算結果
成功返回0;失敗返回錯誤碼
pthread_exit函數線程終止
void pthread_exit(void *value_ptr);
value_ptr:value_ptr不要指向一個局部變量。
返回值:無返回值,跟進程一樣,線程結束的時候無法返回到它的調用者(自身)
pthread_cancel函數取消一個執行中的線程
int pthread_cancel(pthread_t thread);
參數
thread:線程ID
成功返回0;失敗返回錯誤碼
在進程中,如果父進程不管子進程,需要忽略SIGCHLD信號
pthread_detach函數將一個線程分離
int pthread_detach(pthread_t thread);
thread:線程ID
返回值:成功返回0;失敗返回錯誤碼
脫離進程運行的函數,
一般情況下,調用線程執行體,馬上調用detach函數調用。在父進程中同樣調用pthrea_join,這樣就可以避免了父進程的阻塞狀態。
錯誤檢查
傳統的一些函數是,成功返回0,失敗返回-1,並且對全局變量errno賦值以指示錯誤。
pthreads函數出錯時不會設置全局變量errno(而大部分其他POSIX函數會這樣做)。而是將錯誤代碼通過返回值返回。
pthreads同樣也提供了線程內的errno變量,以支持其它使用errno的代碼。對於pthreads函數的錯誤,建議通過返回值業判定,因為讀取返回值要比讀取線程內的errno變量的開銷更小
線程和進程之間傳遞數據
通過全局變量來傳遞數據
通過參數來傳遞數據,通過參數來傳遞的數據不能是線程棧的局部變量,因為線程棧的局部變量回被銷毀。
如果使用pthread_detach函數,用pthread_exit是不能將數據給甩出去數據。但是使用return就可以。
使用歐冠pthread_exit()的效果和return的效果是一樣的。
自己測試detach函數和join函數一起使用的時候不穩定
一般情況下很少在線程中將運算結果給甩出來,線程的運行結果告訴父進程,只需要一個int變量就可以了。
如果父進程死了子線程也會死,這個時候就需要聯合考慮join和detach函數了,以及子線程還有父線程結束先後的問題了。
線程的屬性 設置 (一般使用默認屬性就可以了)
線程分離屬性
線程的棧屬性 10M
棧溢出保護區、
線程競爭範圍
線程的調度策略 隨機的
設置調度優先級 默認是0
並發性 按照自己最適合的方式去映射
進程互斥用信號量,線程也可以用信號量來互斥。信號量很大
線程也有鎖,線程鎖,用的最多的就是POXIS互斥鎖
分離屬性是用來決定一個線程以什麽樣的方式來終止自己。在非分離情況下,當一個線程結束時,它所占用的系統資源並沒有被釋放,也就是沒有真正的終止。只有當pthread_join()函數返回時,創建的線程才能釋放自己占用的系統資源。而在分離屬性情況下,一個線程結束時立即釋放它所占有的系統資源。這裏要註意的一點是,如果設置一個線程的分離屬性,而這個線程運行又非常快,那麽它很可能在pthread_create()函數返回之前就終止了,它終止以後就可能將線程號和系統資源移交給其他的線程使用。
多線程同步問題:
(1)線程共享進程的資源和地址空間
(2)任何線程對系統資源的操作都會給其他線程帶來影響
多線程同步方法:
互斥鎖 線程級的方式
信號量 進程級別的方式
條件變量
posix互斥鎖
互斥鎖是用一種簡單的加鎖方法來控制對共享資源的原子操作。這個互斥鎖只有兩種狀態,也就是上鎖和解鎖,可以把互斥鎖看作某種意義上的全局變量。在同一時刻只能有一個線程掌握某個互斥鎖,擁有上鎖狀態的線程能夠對共享資源進行操作。若其他線程希望上鎖一個已經被上鎖的互斥鎖,則該線程就會掛起,直到上鎖的線程釋放掉互斥鎖為止。可以說,這把互斥鎖保證讓每個線程對共享資源按順序進行原子操作。
互斥鎖機制主要包括下面的基本函數。
互斥鎖初始化:pthread_mutex_init()
互斥鎖上鎖:pthread_mutex_lock()
互斥鎖判斷上鎖:pthread_mutex_trylock()
互斥鎖接鎖:pthread_mutex_unlock()
消除互斥鎖:pthread_mutex_destroy()
其中,互斥鎖可以分為快速互斥鎖、遞歸互斥鎖和檢錯互斥鎖。這三種鎖的區別主要在於其他未占有互斥鎖的線程在希望得到互斥鎖時是否需要阻塞等待。快速鎖是指調用線程會阻塞直至擁有互斥鎖的線程解鎖為止。遞歸互斥鎖能夠成功地返回,並且增加調用線程在互斥上加鎖的次數,而檢錯互斥鎖則為快速互斥鎖的非阻塞版本,它會立即返回並返回一個錯誤信息。默認屬性為快速互斥鎖。可以通過鎖的屬性來進行設置。
通過鎖機制 ,可以創建一個臨界區,使得臨界區的代碼成為一個原子操作。
只在進程中做競爭的線程稱為用戶線程,在這個系統做競爭的線程映射稱為輕量級進程,對於這麽多線程linux是通過linux線程來進行控制和管理的。
在起線程的時候
不互斥(不枷鎖) 不分離 進程等待 OK
互斥(加鎖) 不分離 進程等待 OK
不互斥(不枷鎖) 分離 進程等待 NOK
不互斥(不枷鎖) 分離 進程不等待 NOK
在線程開發中,避免多個線程同時修改一個變量的值
int pthread_mutex_init(pthread_mutex_t *restrict mutex,const pthread_mutexattr_t *restrict attr);
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_init()函數是以動態方式創建互斥鎖的,參數attr指定了新建互斥鎖的屬性。如果參數attr為空,則使用默認的互斥鎖屬性,默認屬性為快速互斥鎖 。互斥鎖的屬性在創建鎖的時候指定,在LinuxThreads實現中僅有一個鎖類型屬性,不同的鎖類型在試圖對一個已經被鎖定的互斥鎖加鎖時表現不同。
pthread_mutexattr_init()函數成功完成之後會返回零,其他任何返回值都表示出現了錯誤。
函數成功執行後,互斥鎖被初始化為未鎖住態。
互斥鎖屬性
使用互斥鎖(互斥)可以使線程按順序執行。通常,互斥鎖通過確保一次只有一個線程執行代碼的臨界段來同步多個線程。互斥鎖還可以保護單線程代碼。
要更改缺省的互斥鎖屬性,可以對屬性對象進行聲明和初始化。通常,互斥鎖屬性會設置在應用程序開頭的某個位置,以便可以快速查找和輕松修改
銷毀互斥鎖對象
pthread_mutexattr_destroy()可用來取消分配用於維護 pthread_mutexattr_init() 所創建的屬性對象的存儲空間。
pthread_mutexattr_destroy 語法
int pthread_mutexattr_destroy(pthread_mutexattr_t *mattr)
pthread_mutexattr_destroy() 成功完成之後會返回零。其他任何返回值都表示出現了錯誤。如果出現以下情況,該函數將失敗並返回對應的值。EINVAL 描述: 由 mattr 指定的值無效。
線程的同步和互斥,以及一個簡單的PC模型
#include<string.h> #include <stdlib.h> #include <stdio.h> #include <unistd.h> #include <pthread.h> //定義鎖並初始化 pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; //定義條件並初始化 pthread_cond_t cond = PTHREAD_COND_INITIALIZER; #define CUSTOM_COUNT 2 #define PRODUCT_COUNT 4 int g_Count = 0; void *consume(void* arg) { int inum = (int)arg; while(1) { pthread_mutex_lock(&mutex); printf("consum %d\n", inum); while(g_Count == 0) { printf("consum:%d 開始等待\n", inum); pthread_cond_wait(&cond, &mutex); printf("consum:%d 醒來\n", inum); } printf("consum:%d 消費產品begin\n", inum); g_Count--; //消費產品 printf("consum:%d 消費產品end\n", inum); pthread_mutex_unlock(&mutex); sleep(1); } pthread_exit(0); } //生產者線程 void *produce(void* arg) { int inum = (int)arg; while(1) { pthread_mutex_lock(&mutex); if(g_Count>17) { pthread_mutex_unlock(&mutex); sleep(1); } else { pthread_mutex_unlock(&mutex); } pthread_mutex_lock(&mutex); printf("產品數量: %d\n", g_Count); printf("produce:%d 生產產品begin\n", inum); g_Count++; //只要我生產出一個產品,就告訴消費者去消費 printf("produce:%d 生產產品end\n", inum); printf("produce:%d 發條件signal begin\n", inum); pthread_cond_signal(&cond); //通知,在條件上等待的線程 printf("produce:%d 發條件signal end\n", inum); pthread_mutex_unlock(&mutex); sleep(1); } printf("produce %d\n", inum); pthread_exit(0); } int main() { pthread_t tidArray[CUSTOM_COUNT+PRODUCT_COUNT]; //創建消費者線程 for(int i=0; i<CUSTOM_COUNT; i++) { pthread_create(&(tidArray[i]), NULL, consume, (void *)i); } //創建生產線程 for (int i=0; i<PRODUCT_COUNT; i++) { pthread_create(&(tidArray[i+CUSTOM_COUNT]), NULL, produce,(void *)i); } for (int i=0; i<CUSTOM_COUNT+PRODUCT_COUNT; i++) { pthread_join(tidArray[i], NULL); //等待線程結束 } return 0; }
Linux Posix Thread