線程是進程內的順序執行流,一個進程中能夠併發多條線程,每條線程並行執行不一樣的任務。html
小聲bb:在FreeRTOS等小型操做系統,或者說是任務調度微內核裏面好像沒有進程的概念.安全
引用"阮一峯"的博客內容bash
CPU:工廠; 假定工廠的電力有限,一次只能供給一個車間使用。也就是說,一個車間開工的時候,其餘車間都必須停工。背後的含義就是,單個CPU一次只能運行一個任務。多線程
進程:車間; 進程就比如工廠的車間,它表明CPU所能處理的單個任務。任一時刻,CPU老是運行一個進程,其餘進程處於非運行狀態。併發
一個車間裏,能夠有不少工人。他們協同完成一個任務。函數
線程:工人; 線程就比如車間裏的工人。一個進程能夠包括多個線程。操作系統
車間的空間是工人們共享的,好比許多房間(內存空間)是每一個工人均可以進出的。這象徵一個進程的內存空間是共享的,每一個線程均可以使用這些共享內存。線程
但是,每間房間的大小不一樣,有些房間最多隻能容納一我的,好比廁所。裏面有人的時候,其餘人就不能進去了。這表明一個線程使用某些共享內存時,其餘線程必須等它結束,才能使用這一塊內存。設計
一個防止他人進入的簡單方法,就是門口加一把鎖。先到的人鎖上門,後到的人看到上鎖,就在門口排隊,等鎖打開再進去。這就叫"互斥鎖"(Mutual exclusion,縮寫 Mutex),防止多個線程同時讀寫某一塊內存區域。指針
還有些房間,能夠同時容納n我的,好比廚房。也就是說,若是人數大於n,多出來的人只能在外面等着。這比如某些內存區域,只能供給固定數目的線程使用。
時的解決方法,就是在門口掛n把鑰匙。進去的人就取一把鑰匙,出來時再把鑰匙掛回原處。後到的人發現鑰匙架空了,就知道必須在門口排隊等着了。這種作法叫作"信號量"(Semaphore),用來保證多個線程不會互相沖突。
不難看出,mutex是semaphore的一種特殊狀況(n=1時)。也就是說,徹底能夠用後者替代前者。可是,由於mutex較爲簡單,且效率高,因此在必須保證資源獨佔的狀況下,仍是採用這種設計。
方便通訊和數據交換
線程間有方便的通訊和數據交換機制。對不一樣進程來講,它們具備獨立的數據空間,要進行數據的傳遞只能經過通訊的方式進行,這種方式不只費時,並且很不方便。線程則否則,因爲同一進程下的線程之間共享數據空間,因此一個線程的數據能夠直接爲其它線程所用,這不只快捷,並且方便。
更高效的利用CPU
使用多線程能夠提升應用程序響應(說明多線程也是輪轉的)。這對圖形界面的程序尤爲有意義,當一個操做耗時很長時,整個系統都會等待這個操做,此時程序不會響應鍵盤、鼠標、菜單的操做,而使用多線程技術,將耗時長的操做置於一個新的線程,能夠避免這種尷尬的狀況。
POSIX Threads(一般簡稱爲 Pthreads)定義了建立和操縱線程的一套 API 接口, 通常用於 Unix-like POSIX 系統中(如 FreeBSD、 GNU/Linux、 OpenBSD、 Mac OS 等系統)。
Pthreads接口根據功能劃分:
寫Pthreads多線程程序的時遠源碼須要包含pthread.h頭文件,LDFLAGS += -pthread,能夠用來指定須要包含的庫。Makefile 選項 CFLAGS 、LDFLAGS 、LIBS能夠了解一下。
定義:能夠看作是線程的句柄,用來引用一個線程
pthread_self函數
pthread_equal函數
進程的終止
一、直接調用exit()。任何一個線程調用exit()都會致使進程退出
二、執行main()函數中的return。 進程的主函數終止了進程也就結束了
三、經過進程的某個其餘線程調用exit()函數。任何一個線程調用exit()都會致使進程退出
主線程、子線程調用exit, pthread_exit,互相產生的影響。
一、在主線程中,在main函數中return了或是調用了exit函數,則主線程退出,且整個進程也會終止,此時進程中的全部線程也將終止。所以要避免main函數過早結束。【隱式調用】
任何線程調用exit()都會致使進程結束。
主線程的main函數跑到return語句會致使進程結束。
主線程/進程的結束致使全部線程的結束。
二、在主線程中調用pthread_exit, 則僅僅是主線程結束,進程不會結束,進程內的其餘線程也不會結束,直到全部線程結束,進程纔會終止。
調用pthread_exit的線程只會結束本線程,主線程調用也只會結束自身,不會結束進程。
三、在任何一個線程中調用exit函數都會致使進程結束。進程一旦結束,那麼進程中的全部線程都將結束。
進程內任何地方調用exit()都會結束進程。
主線程
若是主線程在建立了其它線程後沒有任務須要處理,那麼它應該阻塞等待全部線程都結束爲止,或者應該調用pthread_exit(NULL)。
調用pthread_exit(NULL)能夠減小一個線程開銷,看一下線程怎麼阻塞。
pthread_exit()函數
pthread_exit(void *ptr) 函數使線程退出,並返回一個空指針類型的值。
pthread_join(pthread_t tid,void **rtval)調用此函數的進程/線程等id爲tid的線程返回或被終止,並從它那裏得到返回值。
注意,退出函數返回的是一個空指針類型,接收函數也必須用一個指針來接收。可是函數給出的參數是接收指針的地址,即,接收到的指針值寫入給出的地址處的指針變量。
#include "pthread.h" #include "stdio.h" #include "stdlib.h" #define NUM_THREADS 5 void *PrintHello(void *threadid) { long tid; tid = (long)threadid; printf("Hello World!It's me,thread #%ld!\n",tid); pthread_exit(NULL); } int main(int argc,char* argv[]) { pthread_t threads[NUM_THREADS]; int rc; long t; for (t=0;t<NUM_THREADS;t++) { printf("In main:creating thread %ld\n",t); rc = pthread_create(&threads[t],NULL,PrintHello,(void*)t); if(rc) { printf("ERROR;return code from pthread_create() is %d\n",rc); exit(-1); } } printf("In main:exit!\n"); pthread_exit(NULL); return 0; }
編譯的時候須要加lpthread的庫:
gcc thread_begin_end.c -lpthread輸出以下:
In main:creating thread 0 In main:creating thread 1 Hello World!It's me,thread #0! In main:creating thread 2 Hello World!It's me,thread #1! In main:creating thread 3 Hello World!It's me,thread #2! In main:creating thread 4 Hello World!It's me,thread #3! In main:exit! Hello World!It's me,thread #4!進程內的線程是共享資源的,
In main:creating thread
跟In main:exit!
是主線程打印出來的;
Hello World!It's me,thread #
好像是子線程打印的,須要看一下pthread_create
的形參,形參3是線程開始時候調用的函數,因此就是子線程打印的。
線程能夠分爲分離線程(DETACHED)和非分離線程(JOINABLE)兩種。
分離線程退出時不會報告線程狀態
int pthread_detach(pthread_t thread);
int pthread_join(pthread_t thread, void **retval);
#include "pthread.h" #include "stdio.h" #include "stdlib.h" #include "math.h" #define NUM_THREADS 4 void *BusyWork(void* t) { int i; long tid; double result=0.0; tid = (long)t; printf("Thread %ld starting...\n",tid); for(i=0;i<1000000;i++) { result = result + sin(i)*tan(i); } printf("Thread %ld done.Result =%e\n",tid,result); pthread_exit((void*)t); } int main(int argc,char* argv[]) { pthread_t thread[NUM_THREADS]; int rc; long t; void *status; for(t=0;t<NUM_THREADS;t++) { printf("Main: creating thread %ld\n", t); rc = pthread_create(&thread[t], NULL, BusyWork, (void *)t); if(rc) { printf("ERROR; return code from pthread_create() is %d\n", rc); exit(-1); } } for(t=0;t<NUM_THREADS;t++) { rc = pthread_join(thread[t], &status); if(rc) { printf("ERROR; return code from pthread_join() is %d\n", rc); exit(-1); } printf("Main: completed join with thread %ld having a status of %ld\n",t,(long)status); } printf("Main: program completed. Exiting.\n"); pthread_exit(NULL); }
編譯須要是用:
gcc thread_join.c -lpthread -lm輸出以下:
Main: creating thread 0 Main: creating thread 1 Thread 0 starting... Main: creating thread 2 Thread 1 starting... Main: creating thread 3 Thread 2 starting... Thread 3 starting... Thread 0 done.Result =-3.153838e+06 Thread 3 done.Result =-3.153838e+06 Thread 1 done.Result =-3.153838e+06 Main: completed join with thread 0 having a status of 0 Main: completed join with thread 1 having a status of 1 Thread 2 done.Result =-3.153838e+06 Main: completed join with thread 2 having a status of 2 Main: completed join with thread 3 having a status of 3 Main: program completed. Exiting.
Main: creating thread
是主線程打印出來的,子線程初始化執行的都是BusyWork
函數內的內容。主線程循環調用
pthread_join
函數,應該是將主線程掛起,執行到4遍for循環裏面的第一個pthread_join
函數以後就掛起,等待第一個建立的子線程結束。
Thread x done.Result =
打印沒什麼特別的,線程的調度是隨機的,不肯定哪一個先結束,隨意沒有前後順序。
Main: completed join with thread x having a status of
打印有兩個特徵,一個是順序打印,緣由是在主線程裏順序執行,另外一個就是必須在對應的子線程的後面。
掛起跟阻塞的區別:
理解一:掛起是一種主動行爲,所以恢復也應該要主動完成,而阻塞則是一種被動行爲,是在等待事件或資源時任務的表現,你不知道他何時被阻塞(pend),也就不能確切 的知道他何時恢復阻塞。並且掛起隊列在操做系統裏能夠當作一個,而阻塞隊列則是不一樣的事件或資源(如信號量)就有本身的隊列。
理解二:阻塞(pend)就是任務釋放CPU,其餘任務能夠運行,通常在等待某種資源或信號量的時候出現。掛起(suspend)不釋放CPU,若是任務優先級高就永遠輪不到其餘任務運行,通常掛起用於程序調試中的條件中斷,當出現某個條件的狀況下掛起,而後進行單步調試。
理解三:pend是task主動去等一個事件,或消息.suspend是直接懸掛task,之後這個task和你沒任何關係,任何task間的通訊或者同步都和這個suspended task沒任何關係了,除非你resume task;
理解四:任務調度是操做系統來實現的,任務調度時,直接忽略掛起狀態的任務,可是會顧及處於pend下的任務,當pend下的任務等待的資源就緒後,就能夠轉爲ready了。ready只須要等待CPU時間,固然,任務調度也佔用開銷,可是不大,能夠忽略。能夠這樣理解,只要是掛起狀態,操做系統就不在管理這個任務了。
理解五:掛起是主動的,通常須要用掛起函數進行操做,若沒有resume的動做,則此任務一直不會ready。而阻塞是由於資源被其餘任務搶佔而處於休眠態。二者的表現方式都是從就緒態裏「清掉」,即對應標誌位清零,只不過實現方式不同。
線程基本屬性包括: 棧大小、 調度策略和線程狀態。
初始化屬性對象
int pthread_attr_init(pthread_attr_t *attr);
銷燬屬性對象
int pthread_attr_destroy(pthread_attr_t *attr);
兩種線程狀態
獲取線程狀態
int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate);
設置線程狀態
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
Linux系統線程的默認棧大小爲 8MB,只有主線程的棧大小會在運行過程當中自動增加。
獲取線程棧
int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize);
設置線程棧
intpthread_attr_setstacksize(pthread_attr_t *attr, size_tstacksize);
#include "pthread.h" #include "string.h" #include "stdio.h" #include "stdlib.h" #include "unistd.h" #include "errno.h" #include "ctype.h" #define handle_error_en(en,msg)\ do{errno=en;perror(msg);exit(EXIT_FAILURE);}while(0) #define handle_error(msg)\ do{perror(msg);exit(EXIT_FAILURE);}while(0) //宏定義裏面爲何要使用 do while(0)?由於能夠保證被替換後實現想要的功能 struct thread_info { pthread_t thread_id; int thread_num; char *argv_string; }; static void* thread_start(void *arg)//這塊void*代表函數返回的是一個指針 { struct thread_info *tinfo = arg; char *uargv,*p; printf("Thread %d:top of stack near %p;argv_thing =%s\n",tinfo->thread_num,&p,tinfo->argv_string); uargv = strdup(tinfo->argv_string); if(uargv == NULL) handle_error("strdup"); for(p = uargv;*p!='\0';p++) *p = toupper(*p); return uargv; } int main(int argc,char *argv[]) { int s,tnum,opt,num_threads; struct thread_info *tinfo; pthread_attr_t attr; int stack_size; void *res; stack_size = -1; while((opt = getopt(argc,argv,"s:")) != -1) { switch(opt) { case 's': stack_size = strtoul(optarg,NULL,0); break; default: fprintf(stderr,"Usage:%s[-s stack-size] arg...\n",argv[0]); exit(EXIT_FAILURE); } } num_threads = argc - optind;//optind哪來的?好像是庫裏面的,沒用extern s = pthread_attr_init(&attr); if(s != 0) handle_error_en(s,"pthread_attr_init"); if(stack_size > 0) { s = pthread_attr_setstacksize(&attr,stack_size); if(s != 0) handle_error_en(s,"pthread_attr_aetstacksize"); } tinfo = calloc(num_threads,sizeof(struct thread_info)); if(tinfo == NULL) handle_error("calloc"); for(tnum = 0;tnum < num_threads;tnum++) { tinfo[tnum].thread_num = tnum +1; tinfo[tnum].argv_string = argv[optind + tnum]; s = pthread_create(&tinfo[tnum].thread_id,&attr,&thread_start,&tinfo[tnum]); if(s!=0) handle_error_en(s,"pthread_create"); } s = pthread_attr_destroy(&attr); if(s!=0) handle_error_en(s,"pthread_attr_destory"); for(tnum = 0;tnum < num_threads;tnum++) { s = pthread_join(tinfo[tnum].thread_id,&res); if(s!=0) handle_error_en(s,"pthread_join"); printf("Joined with thread %d;returned value was %s\n",tinfo[tnum].thread_num,(char*)res); free(res);//free函數 } free(tinfo); exit(EXIT_SUCCESS); }
編譯:
gcc thread_attr.c -lpthread執行:
./a.out -s 0x100000 hola salut servus-s參數須要結合getopt函數進行理解
結果:
Thread 1:top of stack near 0x7fcad561fed0;argv_thing =hola Thread 3:top of stack near 0x7fcad4cbfed0;argv_thing =servus Thread 2:top of stack near 0x7fcad4dcfed0;argv_thing =salut Joined with thread 1;returned value was HOLA Joined with thread 2;returned value was SALUT Joined with thread 3;returned value was SERVUS
Tips
getopt函數:
使用man 3 getopt能夠獲取到關於getopt函數的相關信息
用來解析命令的參數,在unidtd.h文件裏面被包含,
須要結合optarg、optind等變量使用。
宏定義使用do while(0),能夠保證被替換後實現想要的功能
#define handle_error(msg)\ do{perror(msg);exit(EXIT_FAILURE);}while(0)
待總結
待總結
待總結