Pthread并發(fā)編程之線程基本元素和狀態(tài)的剖析
前言
在本篇文章當(dāng)中講主要給大家介紹 pthread
并發(fā)編程當(dāng)中關(guān)于線程的基礎(chǔ)概念,并且深入剖析進(jìn)程的相關(guān)屬性和設(shè)置,以及線程在內(nèi)存當(dāng)中的布局形式,幫助大家深刻理解線程。
深入理解 pthread_create
基礎(chǔ)例子介紹
在深入解析 pthread_create
之前,我們先用一個(gè)簡(jiǎn)單的例子簡(jiǎn)單的認(rèn)識(shí)一下 pthread,我們使用 pthread 創(chuàng)建一個(gè)線程并且打印 Hello world 字符串。
#include?<stdio.h> #include?<pthread.h> void*?func(void*?arg)?{ ??printf("Hello?World?from?tid?=?%ld\n",?pthread_self());?//?pthread_self?返回當(dāng)前調(diào)用這個(gè)函數(shù)的線程的線程?id ??return?NULL; } int?main()?{ ??pthread_t?t;?//?定義一個(gè)線程 ??pthread_create(&t,?NULL,?func,?NULL);?//?創(chuàng)建線程并且執(zhí)行函數(shù)?func? ??//?wait?unit?thread?t?finished ??pthread_join(t,?NULL);?//?主線程等待線程?t?執(zhí)行完成然后主線程才繼續(xù)往下執(zhí)行 ??printf("thread?t?has?finished\n"); ??return?0; }
編譯上述程序:
clang?helloworld.c?-o?helloworld.out?-lpthread 或者 gcc?helloworld.c?-o?helloworld.out?-lpthread
在上面的代碼當(dāng)中主線程(可以認(rèn)為是執(zhí)行主函數(shù)的線程)首先定義一個(gè)線程,然后創(chuàng)建線程并且執(zhí)行函數(shù) func ,當(dāng)創(chuàng)建完成之后,主線程使用 pthread_join 阻塞自己,直到等待線程 t 執(zhí)行完成之后主線程才會(huì)繼續(xù)往下執(zhí)行。
我們現(xiàn)在仔細(xì)分析一下 pthread_create
的函數(shù)簽名,并且對(duì)他的參數(shù)進(jìn)行詳細(xì)分析:
int?pthread_create(pthread_t?*thread,?const?pthread_attr_t?*attr, ??????????????????????????void?*(*start_routine)?(void?*),?void?*arg);
- 參數(shù) thread 是一個(gè)類型為 pthread_t 的指針對(duì)象,將這個(gè)對(duì)象會(huì)在 pthread_create 內(nèi)部會(huì)被賦值為存放線程 id 的地址,在后文當(dāng)中我們將使用一個(gè)例子仔細(xì)的介紹這個(gè)參數(shù)的含義。
- 參數(shù) attr 是一個(gè)類型為 pthread_attr_t 的指針對(duì)象,我們可以在這個(gè)對(duì)象當(dāng)中設(shè)置線程的各種屬性,比如說(shuō)線程取消的狀態(tài)和類別,線程使用的棧的大小以及棧的初始位置等等,在后文當(dāng)中我們將詳細(xì)介紹這個(gè)屬性的使用方法,當(dāng)這個(gè)屬性為 NULL 的時(shí)候,使用默認(rèn)的屬性值。
- 參數(shù) start_routine 是一個(gè)返回類型為 void* 參數(shù)類型為 void* 的函數(shù)指針,指向線程需要執(zhí)行的函數(shù),線程執(zhí)行完成這個(gè)函數(shù)之后線程就會(huì)退出。
- 參數(shù) arg ,傳遞給函數(shù) start_routine 的一個(gè)參數(shù),在上一條當(dāng)中我們提到了 start_routine 有一個(gè)參數(shù),是一個(gè) void 類型的指針,這個(gè)參數(shù)也是一個(gè) void 類型的指針,在后文當(dāng)中我們使用一個(gè)例子說(shuō)明這個(gè)參數(shù)的使用方法。
深入理解參數(shù) thread
在下面的例子當(dāng)中我們將使用 pthread_self 得到線程的 id ,并且通過(guò)保存線程 id 的地址的變量 t 得到線程的 id ,對(duì)兩個(gè)得到的結(jié)果進(jìn)行比較。
#include?<stdio.h> #include?<pthread.h> void*?func(void*?arg)?{ ??printf("線程自己打印線程\tid?=?%ld\n",?pthread_self()); ??return?NULL; } int?main()?{ ??pthread_t?t; ??pthread_create(&t,?NULL,?func,?NULL); ??printf("主線程打印線程?t?的線程?id?=?%ld\n",?*(long*)(&t)); ??pthread_join(t,?NULL); ??return?0; }
上面程序的執(zhí)行結(jié)果如下圖所示:
根據(jù)上面程序打印的結(jié)果我們可以知道,變量 pthread_t t
保存的就是線程 id 的地址, 參數(shù) t 和線程 id 之間的關(guān)系如下所示:
在上面的代碼當(dāng)中我們首先對(duì) t 取地址,然后將其轉(zhuǎn)化為一個(gè) long 類型的指針,然后解引用就可以得到對(duì)應(yīng)地址的值了,也就是線程的ID。
深入理解參數(shù) arg
在下面的程序當(dāng)中我們定義了一個(gè)結(jié)構(gòu)體用于保存一些字符出的信息,然后創(chuàng)建一個(gè)這個(gè)結(jié)構(gòu)體的對(duì)象,將這個(gè)對(duì)象的指針作為參數(shù)傳遞給線程要執(zhí)行的函數(shù),并且在線程內(nèi)部打印字符串當(dāng)中的內(nèi)容。
#include?<stdio.h> #include?<pthread.h> #include?<malloc.h> #include?<stdlib.h> #include?<string.h> typedef?struct?info?{ ??char?s[1024];?//?保存字符信息 ??int??size;????//?保存字符串的長(zhǎng)度 }info_t; static void*?func(void*?arg)?{ ??info_t*?in?=?(info_t*)arg; ??in->s[in->size]?=?'\0'; ??printf("string?in?arg?=?%s\n",?in->s); ??return?NULL; } int?main()?{ ??info_t*?in?=?malloc(sizeof(info_t));?//?申請(qǐng)內(nèi)存空間 ??//?保存?HelloWorld?這個(gè)字符串?并且設(shè)置字符串的長(zhǎng)度 ??in->s[0]?=?'H'; ??in->s[1]?=?'e'; ??in->s[2]?=?'l'; ??in->s[3]?=?'l'; ??in->s[4]?=?'o'; ??in->s[5]?=?'W'; ??in->s[6]?=?'o'; ??in->s[7]?=?'r'; ??in->s[8]?=?'l'; ??in->s[9]?=?'d'; ??in->size?=?10; ??pthread_t?t;?????????//?將?in?作為參數(shù)傳遞給函數(shù)?func ??pthread_create(&t,?NULL,?func,?(void*)in);? ??pthread_join(t,?NULL); ??free(in);?//?釋放內(nèi)存空間 ??return?0; }
上面程序的執(zhí)行結(jié)果如下所示:
可以看到函數(shù)參數(shù)已經(jīng)做到了正確傳遞。
深入理解參數(shù) attr
在深入介紹參數(shù) attr 前,我們首先需要了解一下程序的內(nèi)存布局,在64位操作系統(tǒng)當(dāng)中程序的虛擬內(nèi)存布局大致如下所示,從下往上依次為:只讀數(shù)據(jù)/代碼區(qū)、可讀可寫數(shù)據(jù)段、堆區(qū)、共享庫(kù)的映射區(qū)、程序棧區(qū)以及內(nèi)核內(nèi)存區(qū)域。我們程序執(zhí)行的區(qū)域就是在棧區(qū)。
根據(jù)上面的虛擬內(nèi)存布局示意圖,我們將其簡(jiǎn)化一下得到單個(gè)線程的執(zhí)行流和大致的內(nèi)存布局如下所示(程序執(zhí)行的時(shí)候有他的棧幀以及寄存器現(xiàn)場(chǎng),圖中將寄存器也做出了標(biāo)識(shí)):
程序執(zhí)行的時(shí)候當(dāng)我們進(jìn)行函數(shù)調(diào)用的時(shí)候函數(shù)的棧幀就會(huì)從上往下生長(zhǎng),我們現(xiàn)在進(jìn)行一下測(cè)試,看看程序的棧幀最大能夠達(dá)到多少。
#include?<stdio.h> #include?<stdlib.h> #include?<sys/types.h> #include?<unistd.h> int?times?=?1; void*?func(void*?arg)?{ ??char?s[1?<<?20];?//?申請(qǐng)?1MB?內(nèi)存空間(分配在??臻g上) ??printf("times?=?%d\n",?times); ??times++; ??func(NULL); ??return?NULL; } int?main()?{ ??func(NULL); ??return?0; }
上述程序的執(zhí)行結(jié)果如下圖所示:
從上面的程序我們可以看到在第 8 次申請(qǐng)棧內(nèi)存的時(shí)候遇到了段錯(cuò)誤,因此可以判斷??臻g大小在 8MB 左右,事實(shí)上我們可以查看 linux 操作系統(tǒng)上,棧內(nèi)存的指定大小:
事實(shí)上在 linux 操作系統(tǒng)當(dāng)中程序的??臻g的大小默認(rèn)最大為 8 MB。
現(xiàn)在我們來(lái)測(cè)試一下,當(dāng)我們創(chuàng)建一個(gè)線程的時(shí)候,線程的棧的大小大概是多少:
#include?<stdio.h> #include?<pthread.h> #include?<stdlib.h> #include?<sys/types.h> #include?<unistd.h> int?times?=?1; void*?func(void*?arg)?{ ??printf("times?=?%d\n",?times); ??times++; ??char?s[1?<<?20];?//?申請(qǐng)?1MB?內(nèi)存空間(分配在棧空間上) ??func(NULL); ??return?NULL; } int?main()?{ ??pthread_t?t; ??pthread_create(&t,?NULL,?func,?NULL); ??pthread_join(t,?NULL); ??return?0; }
上面的程序執(zhí)行結(jié)果如下圖所示,可以看到當(dāng)我們創(chuàng)建一個(gè)線程的時(shí)候棧的最大的大小也是 8MB。
設(shè)置線程棧空間的大小
現(xiàn)在如果我們有一個(gè)需求,需要的??臻g大于 8MB,我們應(yīng)該怎么辦呢?這就是我們所需要談到的 attr,這個(gè)變量是一個(gè) pthread_attr_t 對(duì)象,這個(gè)對(duì)象的主要作用就是用于設(shè)置線程的各種屬性的,其中就包括線程的棧的大小,在下面的程序當(dāng)中我們將線程的??臻g的大小設(shè)置成 24MB,并且使用程序進(jìn)行測(cè)試。
#include?<stdio.h> #include?<pthread.h> #include?<stdlib.h> #define?MiB?*?1?<<?20 int?times?=?0; void*?stack_overflow(void*?args)?{ ??printf("times?=?%d\n",?++times); ??char?s[1?<<?20];?//?1?MiB ??stack_overflow(NULL); ??return?NULL; } int?main()?{ ??pthread_attr_t?attr; ??pthread_attr_init(&attr);?//?對(duì)變量?attr?進(jìn)行初始化操作 ??pthread_attr_setstacksize(&attr,?24?MiB);?//?設(shè)置棧幀大小為?24?MiB?這里使用了一個(gè)小的?trick?大家可以看一下?MiB?的宏定義 ??pthread_t?t; ??pthread_create(&t,?&attr,?stack_overflow,?NULL); ??pthread_join(t,?NULL); ??pthread_attr_destroy(&attr);?//?釋放線程屬性的相關(guān)資源 ??return?0; }
上面的程序執(zhí)行結(jié)果如下圖所示:
從上面程序的執(zhí)行結(jié)果來(lái)看我們?cè)O(shè)置的 24 MB 的??臻g大小起到了效果,我們可以通過(guò)線程的遞歸次數(shù)可以看出來(lái)我們確實(shí)申請(qǐng)了那么大的空間。在上面的程序當(dāng)中我們對(duì)屬性的操作如下,這也是對(duì)屬性操作的一般流程:
- 使用
pthread_attr_init
對(duì)屬性變量進(jìn)行初始化操作。 - 使用各種各樣的函數(shù)對(duì)屬性 attr 進(jìn)行操作,比如
pthread_attr_setstacksize
,這個(gè)函數(shù)的作用就是用于設(shè)置線程的棧空間的大小。 - 使用
pthread_attr_destroy
釋放線程屬性相關(guān)的系統(tǒng)資源。
自己為線程的棧申請(qǐng)空間
在上一小節(jié)當(dāng)中我們通過(guò)函數(shù) pthread_attr_setstacksize
給棧空間設(shè)置了新的大小,并且使用程序檢查驗(yàn)證了新設(shè)置的??臻g大小,在這一小節(jié)當(dāng)中我們將介紹使用我們自己申請(qǐng)的內(nèi)存空間也可以當(dāng)作線程的棧使用。我們將使用兩種方法取驗(yàn)證這一點(diǎn):
- 使用
malloc
函數(shù)申請(qǐng)內(nèi)存空間,這部分空間主要在堆當(dāng)中。 - 使用
mmap
系統(tǒng)調(diào)用在共享庫(kù)的映射區(qū)申請(qǐng)內(nèi)存空間。
使用 malloc 函數(shù)申請(qǐng)內(nèi)存空間
#include?<stdio.h> #include?<pthread.h> #include?<stdlib.h> #define?MiB?*?1?<<?20 int?times?=?0; static void*?stack_overflow(void*?args)?{ ??printf("times?=?%d\n",?++times); ??char?s[1?<<?20];?//?1?MiB ??stack_overflow(NULL); ??return?NULL; } int?main()?{ ??pthread_attr_t?attr; ??pthread_attr_init(&attr); ??void*?stack?=?malloc(2?MiB);?//?使用?malloc?函數(shù)申請(qǐng)內(nèi)存空間?申請(qǐng)的空間大小為?2?MiB? ??pthread_t?t; ??pthread_attr_setstack(&attr,?stack,?2?MiB);?//?使用屬性設(shè)置函數(shù)設(shè)置棧的位置?棧的最低地址為?stack?棧的大小等于?2?MiB? ??pthread_create(&t,?&attr,?stack_overflow,?NULL); ??pthread_join(t,?NULL); ??pthread_attr_destroy(&attr);?//?釋放系統(tǒng)資源 ??free(stack);?//?釋放堆空間 ??return?0; }
上述程序的執(zhí)行結(jié)果如下圖所示:
從上面的執(zhí)行結(jié)果可以看出來(lái)我們?cè)O(shè)置的??臻g的大小為 2MB 成功了。在上面的程序當(dāng)中我們主要使用 pthread_attr_setstack
函數(shù)設(shè)置棧的低地址和棧空間的大小。我們申請(qǐng)的內(nèi)存空間內(nèi)存布局大致如下圖所示:
使用 mmap 申請(qǐng)內(nèi)存作為棧空間
#define?_GNU_SOURCE #include?<stdio.h> #include?<pthread.h> #include?<stdlib.h> #include?<sys/mman.h> #define?MiB?*?1?<<?20 #define?STACK_SIZE?2?MiB int?times?=?0; static void*?stack_overflow(void*?args)?{ ??printf("times?=?%d\n",?++times); ??char?s[1?<<?20];?//?1?MiB ??stack_overflow(NULL); ??return?NULL; } int?main()?{ ??pthread_attr_t?attr; ??pthread_attr_init(&attr); ??void*?stack?=?mmap(NULL,?STACK_SIZE,?PROT_READ?|?PROT_WRITE, ????????????????MAP_PRIVATE?|?MAP_ANONYMOUS?|?MAP_STACK,?-1,?0); ??if?(stack?==?MAP_FAILED) ??????perror("mapped?error:"); ??pthread_t?t; ??pthread_attr_setstack(&attr,?stack,?STACK_SIZE); ??pthread_create(&t,?&attr,?stack_overflow,?NULL); ??pthread_join(t,?NULL); ??pthread_attr_destroy(&attr); ??free(stack); ??return?0; }
在上面的程序當(dāng)中我們使用 mmap 系統(tǒng)調(diào)用在共享庫(kù)空間申請(qǐng)了一段內(nèi)存空間,并且將其做為棧空間,我們?cè)谶@里就不將程序執(zhí)行的結(jié)果放出來(lái)了,上面整個(gè)程序和前面的程序相差不大,只是在申請(qǐng)內(nèi)存方面發(fā)生了變化,總體的方向是不變的。
根據(jù)前面知識(shí)的學(xué)習(xí),我們可以知道多個(gè)線程可以共享同一個(gè)進(jìn)程虛擬地址空間,我們只需要給每個(gè)線程申請(qǐng)一個(gè)??臻g讓線程執(zhí)行起來(lái)就行,基于此我們可以知道多個(gè)線程的執(zhí)行流和大致的內(nèi)存布局如下圖所示:
在上圖當(dāng)中不同的線程擁有不同的??臻g和每個(gè)線程自己的寄存器現(xiàn)場(chǎng),正如上圖所示,??臻g可以是在堆區(qū)也可以是在共享庫(kù)的映射區(qū)域,只需要給線程提供??臻g即可。
深入理解線程的狀態(tài)
在 pthread
當(dāng)中給我們提供了一個(gè)函數(shù) pthread_cancel
可以取消一個(gè)正在執(zhí)行的線程,取消正在執(zhí)行的線程之后會(huì)將線程的退出狀態(tài)(返回值)設(shè)置成宏定義 PTHREAD_CANCELED
。我們使用下面的例子去理解一下線程取消的過(guò)程:
#include?<stdio.h> #include?<pthread.h> #include?<assert.h> void*?task(void*?arg)?{ ?while(1)?{ ????pthread_testcancel();?//?測(cè)試是否被取消執(zhí)行了 ??} ??return?NULL; } int?main()?{ ??void*?res; ??pthread_t?t; ??pthread_create(&t,?NULL,?task,?NULL); ??int?s?=?pthread_cancel(t);?//?取消函數(shù)的執(zhí)行 ??if(s?!=?0) ????fprintf(stderr,?"cancel?failed\n"); ??pthread_join(t,?&res); ??assert(res?==?PTHREAD_CANCELED); ??return?0; }
在上面的程序當(dāng)中我們?cè)谥骶€程當(dāng)中使用函數(shù) pthread_cancel
函數(shù)取消線程的執(zhí)行,編譯執(zhí)行上面的程序是可以通過(guò)的,也就是說(shuō)程序正確執(zhí)行了,而且 assert 也通過(guò)了。我們先不仔細(xì)去分析上面的代碼的執(zhí)行流和函數(shù)的意義。我們先需要了解一個(gè)線程的基本特性。
與線程取消執(zhí)行相關(guān)的一共有兩個(gè)屬性,分別是:
1.取消執(zhí)行的狀態(tài),線程的取消執(zhí)行的狀態(tài)一共有兩個(gè):
- PTHREAD_CANCEL_ENABLE:這個(gè)狀態(tài)表示這個(gè)線程是可以取消的,也是線程創(chuàng)建時(shí)候的默認(rèn)狀態(tài)。
- PTHREAD_CANCEL_DISABLE:這個(gè)狀態(tài)表示線程是不能夠取消的,如果有一個(gè)線程發(fā)送了一個(gè)取消請(qǐng)求,那么這個(gè)發(fā)送取消消息的線程將會(huì)被阻塞直到線程的取消狀態(tài)變成 PTHREAD_CANCEL_ENABLE 。
2.取消執(zhí)行的類型,取消線程執(zhí)行的類型也有兩種:
- PTHREAD_CANCEL_DEFERRED:當(dāng)一個(gè)線程的取消狀態(tài)是這個(gè)的時(shí)候,線程的取消就會(huì)被延遲執(zhí)行,知道線程調(diào)用一個(gè)是取消點(diǎn)的(cancellation point)函數(shù),比如 sleep 和 pthread_testcancel ,所有的線程的默認(rèn)取消執(zhí)行的類型就是這個(gè)類型。
- PTHREAD_CANCEL_ASYNCHRONOUS:如果線程使用的是這個(gè)取消類型那么線程可以在任何時(shí)候被取消執(zhí)行,當(dāng)他接收到了一個(gè)取消信號(hào)的時(shí)候,馬上就會(huì)被取消執(zhí)行,事實(shí)上這個(gè)信號(hào)的實(shí)現(xiàn)是使用 tgkill 這個(gè)系統(tǒng)調(diào)用實(shí)現(xiàn)的。
事實(shí)上我們很少回去使用 PTHREAD_CANCEL_ASYNCHRONOUS ,因?yàn)檫@樣殺死一個(gè)線程會(huì)導(dǎo)致線程還有很多資源沒有釋放,會(huì)給系統(tǒng)帶來(lái)很大的災(zāi)難,比如線程使用 malloc 申請(qǐng)的內(nèi)存空間沒有釋放,申請(qǐng)的鎖和信號(hào)量沒有釋放,尤其是鎖和信號(hào)量沒有釋放,很容易造成死鎖的現(xiàn)象。
有了以上的知識(shí)基礎(chǔ)我們現(xiàn)在可以來(lái)談一談前面的兩個(gè)函數(shù)了:
- pthread_cancel(t) :是給線程 t 發(fā)送一個(gè)取消請(qǐng)求。
- pthread_testcancel():這個(gè)函數(shù)是一個(gè)取消點(diǎn),當(dāng)執(zhí)行這個(gè)函數(shù)的時(shí)候,程序就會(huì)取消執(zhí)行。
現(xiàn)在我們使用默認(rèn)的線程狀態(tài)和類型創(chuàng)建一個(gè)線程執(zhí)行死循環(huán),看看線程是否能夠被取消掉:
#include?<stdio.h> #include?<pthread.h> #include?<assert.h> #include?<unistd.h> void*?task(void*?arg)?{ ??while(1)?{ ???? ??} ??return?NULL; } int?main()?{ ??void*?res; ??pthread_t?t1; ??pthread_create(&t1,?NULL,?task,?NULL); ??int?s?=?pthread_cancel(t1); ??if(s?!=?0)?//?s?==?0?mean?call?successfully ????fprintf(stderr,?"cancel?failed\n"); ??pthread_join(t1,?&res); ??assert(res?==?PTHREAD_CANCELED); ??return?0; }
在上面的代碼當(dāng)中我們啟動(dòng)了一個(gè)線程不斷的去進(jìn)行進(jìn)行死循環(huán)的操作,程序的執(zhí)行結(jié)果為程序不會(huì)終止,因?yàn)橹骶€程在等待線程的結(jié)束,但是線程在進(jìn)行死循環(huán),而且線程執(zhí)行死循環(huán)的時(shí)候沒有調(diào)用一個(gè)是取消點(diǎn)的函數(shù),因此程序不會(huì)終止取消。
下面我們更改程序,將線程的取消類型設(shè)置為 PTHREAD_CANCEL_ASYNCHRONOUS ,在看看程序的執(zhí)行結(jié)果:
#include?<stdio.h> #include?<pthread.h> #include?<assert.h> #include?<unistd.h> void*?task(void*?arg)?{ ??pthread_setcanceltype(PTHREAD_CANCEL_ASYNCHRONOUS,?NULL); ??while(1)?{ ???? ??} ??return?NULL; } int?main()?{ ??void*?res; ??pthread_t?t1; ??pthread_create(&t1,?NULL,?task,?NULL); ??int?s?=?pthread_cancel(t1); ??if(s?!=?0)?//?s?==?0?mean?call?successfully ????fprintf(stderr,?"cancel?failed\n"); ??pthread_join(t1,?&res); ??assert(res?==?PTHREAD_CANCELED); ??return?0; }
在上面的程序當(dāng)中我們?cè)诰€程執(zhí)行的函數(shù)當(dāng)中使用 pthread_setcanceltype
將線程的取消類型設(shè)置成 PTHREAD_CANCEL_ASYNCHRONOUS 這樣的話就能夠在其他線程使用 pthread_cancel 的時(shí)候就能夠立即取消線程的執(zhí)行。
int?pthread_setcanceltype(int?type,?int?*oldtype)
上方是 pthread_setcanceltype 的函數(shù)簽名,在前面的使用當(dāng)中我們只使用了第一個(gè)參數(shù),第二個(gè)參數(shù)我們是設(shè)置成 NULL,第二個(gè)參數(shù)我們可以傳入一個(gè) int 類型的指針,然后會(huì)在將線程的取消類型設(shè)置成 type 之前將前一個(gè) type 拷貝到 oldtype 所指向的內(nèi)存當(dāng)中。
type: 有兩個(gè)參數(shù):PTHREAD_CANCEL_ASYNCHRONOUS 和 PTHREAD_CANCEL_DEFERRED 。
int?pthread_setcancelstate(int?state,?int?*oldstate);
設(shè)置取消狀態(tài)的函數(shù)簽名和上一個(gè)函數(shù)簽名差不多,參數(shù)的含義也是差不多,type 表示需要設(shè)置的取消狀態(tài),有兩個(gè)參數(shù):PTHREAD_CANCEL_ENABLE 和 PTHREAD_CANCEL_DISABLE ,參數(shù) oldstate 是指原來(lái)的線程的取消狀態(tài),如果你傳入一個(gè) int 類型的指針的話就會(huì)將原來(lái)的狀態(tài)保存到指針指向的位置。
其實(shí)關(guān)于線程的一些細(xì)節(jié)還有比較多的內(nèi)容限于篇幅,在本篇文章當(dāng)中主要給大家介紹這些細(xì)節(jié)。
關(guān)于棧大小程序的一個(gè)小疑惑
在上文當(dāng)中我們使用了一個(gè)小程序去測(cè)試線程的??臻g的大小,并且打印函數(shù) func
的調(diào)用次數(shù),每一次調(diào)用的時(shí)候我們都會(huì)申請(qǐng) 1MB 大小的棧空間變量?,F(xiàn)在我們看下面兩個(gè)程序,在下面兩個(gè)程序只有 func
函數(shù)有區(qū)別,而在 func
函數(shù)當(dāng)中主要的區(qū)別就是:
- 在第一個(gè)程序當(dāng)中是先申請(qǐng)內(nèi)存空間,然后再打印變量
times
的值。 - 在第二個(gè)程序當(dāng)中是先打印變量
times
的值,然后再申請(qǐng)內(nèi)存空間。
#include?<stdio.h> #include?<pthread.h> #include?<stdlib.h> #include?<sys/types.h> #include?<unistd.h> int?times?=?1; //?先申請(qǐng)內(nèi)存空間再打印 void*?func(void*?arg)?{ ??char?s[1?<<?20];?//?申請(qǐng)?1MB?內(nèi)存空間(分配在??臻g上) ??printf("times?=?%d\n",?times); ??times++; ??func(NULL); ??return?NULL; } int?main()?{ ??pthread_t?t; ??pthread_create(&t,?NULL,?func,?NULL); ??pthread_join(t,?NULL); ??return?0; }
#include?<stdio.h> #include?<pthread.h> #include?<stdlib.h> #include?<sys/types.h> #include?<unistd.h> int?times?=?1; //?先打印再申請(qǐng)內(nèi)存空間 void*?func(void*?arg)?{ ??printf("times?=?%d\n",?times); ??times++; ??char?s[1?<<?20];?//?申請(qǐng)?1MB?內(nèi)存空間(分配在棧空間上) ??func(NULL); ??return?NULL; } int?main()?{ ??pthread_t?t; ??pthread_create(&t,?NULL,?func,?NULL); ??pthread_join(t,?NULL); ??return?0; }
由于上面兩個(gè)程序的輸出結(jié)果是一樣的,所以我就只放出一個(gè)程序的輸出結(jié)果了:
但是不對(duì)呀!如果是后申請(qǐng)內(nèi)存空間的話,程序的輸出應(yīng)該能夠打印 times = 8
啊,因?yàn)橹爸簧暾?qǐng)了 7MB 的空間,我們打印 times = 8
的時(shí)候還沒有執(zhí)行到語(yǔ)句 char s[1 << 20];
,那為什么也只打印到 7 呢?
出現(xiàn)上面問(wèn)題的主要原因就需要看編譯器給我們編譯后的程序是如何申請(qǐng)內(nèi)存空間的。我們將上面的函數(shù) func
的匯編代碼展示出來(lái):
00000000004005e0 <func>: 4005e0: 55 push %rbp 4005e1: 48 89 e5 mov %rsp,%rbp 4005e4: 48 81 ec 20 00 10 00 sub $0x100020,%rsp 4005eb: 48 8d 04 25 3c 07 40 lea 0x40073c,%rax 4005f2: 00 4005f3: 48 89 7d f8 mov %rdi,-0x8(%rbp) 4005f7: 8b 34 25 40 10 60 00 mov 0x601040,%esi 4005fe: 48 89 c7 mov %rax,%rdi 400601: b0 00 mov $0x0,%al 400603: e8 c8 fe ff ff callq 4004d0 <printf@plt> 400608: 48 bf 00 00 00 00 00 movabs $0x0,%rdi 40060f: 00 00 00 400612: 8b 34 25 40 10 60 00 mov 0x601040,%esi 400619: 81 c6 01 00 00 00 add $0x1,%esi 40061f: 89 34 25 40 10 60 00 mov %esi,0x601040 400626: 89 85 ec ff ef ff mov %eax,-0x100014(%rbp) 40062c: e8 af ff ff ff callq 4005e0 <func> 400631: 48 bf 00 00 00 00 00 movabs $0x0,%rdi 400638: 00 00 00 40063b: 48 89 85 e0 ff ef ff mov %rax,-0x100020(%rbp) 400642: 48 89 f8 mov %rdi,%rax 400645: 48 81 c4 20 00 10 00 add $0x100020,%rsp 40064c: 5d pop %rbp 40064d: c3 retq
上面的匯編代碼是上面的程序在 x86_64 平臺(tái)下得到的,我們需要注意一行匯編指令 sub $0x100020,%rsp
,這條指令的主要作用就是將棧頂往下擴(kuò)展(棧是從上往下生長(zhǎng)的)1 MB 字節(jié)(實(shí)際上稍微比 1MB 大一點(diǎn),因?yàn)檫€有其他操作需要一些??臻g),事實(shí)上就是給變量 s 申請(qǐng) 1MB 的??臻g。
好了,看到這里就破案了,原來(lái)編譯器申請(qǐng)??臻g的方式是將棧頂寄存器 rsp ,往虛擬地址空間往下移動(dòng),而編譯器在函數(shù)執(zhí)行剛開始的時(shí)候就申請(qǐng)了這么大的空間,因此不管是先申請(qǐng)空間再打印,還是先打印再申請(qǐng)空間,在程序被編譯成匯編指令之后,函數(shù) func
在函數(shù)剛開始就申請(qǐng)了對(duì)應(yīng)的空間,因此才出現(xiàn)了都只打印到 times = 7
。
總結(jié)
在本篇文章當(dāng)中主要給大家介紹了線程的基本元素和一些狀態(tài),還重點(diǎn)介紹了各種與線程相關(guān)屬性的函數(shù),主要使用的各種函數(shù)如下:
- pthread_create,用與創(chuàng)建線程
- pthread_attr_init,初始話線程的基本屬性。
- pthread_attr_destroy,釋放屬性相關(guān)資源。
- pthread_join,用于等待線程執(zhí)行完成。
- pthread_attr_setstacksize,用于設(shè)置線程執(zhí)行棧的大小。
- pthread_attr_setstack,設(shè)置線程執(zhí)行棧的棧頂和棧的大小。
- pthread_testcancel,用于檢測(cè)線程是否被取消了,是一個(gè)取消點(diǎn)。
- pthread_cancel,取消一個(gè)線程的執(zhí)行。
以上就是Pthread并發(fā)編程之線程基本元素和狀態(tài)的剖析的詳細(xì)內(nèi)容,更多關(guān)于Pthread線程元素 狀態(tài)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C++ 內(nèi)聯(lián)函數(shù)inline案例詳解
這篇文章主要介紹了C++ 內(nèi)聯(lián)函數(shù)inline案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-09-09C++實(shí)現(xiàn)LeetCode(201.數(shù)字范圍位相與)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(201.數(shù)字范圍位相與),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08C++中VTK9.3.0刻度標(biāo)簽重疊的問(wèn)題記錄
這篇文章主要介紹了C++中VTK9.3.0刻度標(biāo)簽重疊的問(wèn)題,本文采用VTK9.3.0版本,其他版本如VKT8.0亦有同樣的問(wèn)題,需要的朋友可以參考下2024-06-06