介紹
什麼是線程?我們必須首先了解線程操作的邊界。
當計算機程序從某個存儲區加載到計算機內存中並開始執行時,它就變成了一個進程。一個進程可以由一個處理器或一組處理器執行。內存中的進程描述包含重要信息,例如跟蹤程序中當前位置的程序計數器(即當前正在執行的指令)、寄存器、變量存儲、文件句柄、信號等。
線程是程序中的一系列此類指令,可以獨立於其他代碼執行。
上圖概念性地顯示了線程位於同一進程地址空間內,因此,進程的內存描述中存在的大部分信息可以跨線程共享。有些信息是無法複製的,例如堆棧(每個線程指向不同內存區域的堆棧指針)、寄存器和線程特定的數據。這一信息足以允許獨立於程序的主線程和程序內的一個或多個其他線程來調度線程。
建立和使用線程
線程標識符
程序中使用線程標識符 ID 來表示線程。線程 ID 屬於封裝的 pthreadLt 類型。 爲建立線程,你需要在程序中聲明一個 pthread_t
類型的變量。 如果只需在某個函數中使用線程 ID,或者函數直到線程終止時才返回,則可以將線程 ID 聲明爲自動存儲變量,不過大部分時間內, 線程 ID 保存在共享變量中(靜態或外部), 或者保存在堆空間的結構體中。
pthread_t thread;
創建線程
通過向 pthread_create
函數傳送線程函數地址和線程函數調用的參數來創建線程。線程函數應該只有一個 void *
類型參數,並返回相同的類型值。 當創建線程時,pthread_create
函數返回一個 pthread_t
類型的線程 ID, 並保存在 thread 參數中。 通過這個線程 ID, 程序可以引用該線程。
int pthread_create(pthread_t *thread, const pthreae_attr_t *attr, void *(*start)(void *), void *arg);
獲得自己的線程ID
線程可以通過調用 pthread_self
來獲得自身的 ID。除非線程的創建者或者線程本身將線程 ID 保存於某處,否則不可能獲得一個線程的 ID。要對線程進行任何操作都必須通過線程 ID。
pthread_t pthread_self(void);
比較線程
可以使用 pthread_equal
函數來比較兩個線程 ID,只能比較二者是否相同。比較兩個線程 ID 誰大誰小是沒有任何意義的,因爲線程 ID 之間不存在順序。如果兩個線程 ID 表示同一個線程,則 pthread_equal
函數返回非零值,否則返回零值。
int pthread_equal(pthread_t tl, pthread_t t2); // 相等返回非0值
分離線程
如果要創建一個從不需要控制的線程,可以是用屬性(attribute)來建立線程以使它可分離的。如果不想等待創建的某個線程,而且知道不再需要控制它,可以使用 pthread_detach
函數來分離它。 分離一個正在運行的線程不會對線程帶來任何影響,僅僅是通知系統當該線程結束時,其所屬資源可以自動被回收。網絡、多線程服務器常用。
int pthread_detach(pthread_t thread);
退出線程
當 C 程序運行時,首先運行 main
函數。在線程代碼中, 這個特殊的執行流被稱爲 “初始線程” 或 “主線程”。 你可以在初始線程中做任何你能在普通線程中做的事情。也可以調用 pthread_exit
來終止自己。
int pthread_exit(void *value_ptr);
取消線程
外部發送終止信號給指定線程,如果成功則返回0,否則返回非0。發送成功並不意味着線程會終止。 另外,如果一個線程被回收,終止線程的 ID 可能被分配給其他新的線程,使用該 ID 調用 pthread_cancel
可能就會取消一個不同的線程, 而不是返回 ESRCH 錯誤。
int pthread_cancel(pthread_t thread);
等待線程結束
如果需要獲取線程的返回值,或者需要獲知其何時結束,應該調用 pthread_join
函數。 pthread_join
函數將阻塞其調用者直到指定線程終止。然後,可以選擇地保存線程的返回值。調 用 pthread_join
函數將自動分離指定的線程。線程會在返回時被回收,回收將釋放所有在線程終止時未釋放的系統和進程資源,包栝保存線程返回值的內存空間、堆棧、保存寄存器狀態的內存空間等。所以,在線程終止後上述資源就不該被訪問了。
int pthread_join(pthread_t thread, void **value_ptr);
線程生命週期
線程有四種基本狀態:
- 就緒(Ready)狀態。線程能夠運行,但在等待可用的處理器,可能剛剛啓動,或剛剛從阻塞中恢復,或者被其他線程搶佔。
- 運行(Running)狀態。線程正在運行,在多處器系統中,可能有多個線程處於運行態線程由於等待處理器外的其他條件無法運行,如條件變量的改變、加鎖互斥量或 I/O 操作結束。
- 阻塞(Blocked)狀態。線程由於等待處理器外的其他條件無法運行,如條件變量的改變、加鎖互斥量或 I/O 操作結束。
- 終止(Terminated)狀態。線程從起始函數中返回,或調用 pthread_exit,或者被取消,終止自己並完成所有資源清理。不是被分離,也不是被連接,一且線程被分離或者連接,它就可以被收回。
下面是線程的狀態轉換圖:
下面程序展示了一個線程使用的完整生命週期實例:
/*
*
* threads-example.c: Program to demonstrate Pthreads in C
*/
#include <sys/types.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
void *ethread (void *arg);
char ret_status [10] [100];
int main (int argc, char **argv)
{
pthread_t tid [10];
int i, r;
void *status;
char buffer [128];
// Create 10 threads
int thread_no [10];
for (i = 0; i < 10; i++) {
thread_no [i] = i;
if ((r = pthread_create (&tid [i], NULL, ethread, (void *) &thread_no [i])) != 0) {
strerror_r (r, buffer, sizeof (buffer));
fprintf (stderr, "Error = %d (%s)\n", r, buffer); exit (1);
}
}
if ((r = pthread_cancel (tid [9])) != 0) {
strerror_r (r, buffer, sizeof (buffer));
fprintf (stderr, "Error = %d (%s)\n", r, buffer); exit (1);
}
// Wait for threads to terminate
for (i = 0; i < 10; i++) {
if ((r = pthread_join (tid [i], &status)) != 0) {
strerror_r (r, buffer, sizeof (buffer));
fprintf (stderr, "Error = %d (%s)\n", r, buffer); exit (1);
}
if (status == PTHREAD_CANCELED)
printf ("i = %d, status = CANCELED\n", i);
else
printf ("i = %d, status = %s\n", i, (char *) status);
}
exit (0);
}
// ethread: example thread
void *ethread (void *arg)
{
int my_id = *((int *) arg);
// Take a nap
sleep (1);
// say hello and terminate
printf ("Thread %d: Hello World!\n", my_id);
sprintf (ret_status [my_id], "Thread %d: %d", my_id, my_id + 10);
if (my_id == 9) sleep (10);
// pass your id to the thread waiting for you to terminate
// using pthread_join.
pthread_exit (ret_status [my_id]);
}
程序運行結果如下:
bspserver@ubuntu:~/workspace/bin$ ./threads-example
Thread 8: Hello World!
Thread 6: Hello World!
Thread 5: Hello World!
Thread 4: Hello World!
Thread 3: Hello World!
Thread 0: Hello World!
Thread 7: Hello World!
Thread 2: Hello World!
Thread 1: Hello World!
i = 0, status = Thread 0: 10
i = 1, status = Thread 1: 11
i = 2, status = Thread 2: 12
i = 3, status = Thread 3: 13
i = 4, status = Thread 4: 14
i = 5, status = Thread 5: 15
i = 6, status = Thread 6: 16
i = 7, status = Thread 7: 17
i = 8, status = Thread 8: 18
i = 9, status = CANCELED
上面程序中 pthread_create
創建線程後,線程處於就緒狀態。受調度機制的限制,新線程可能在就緒狀態下停留一段時間才被執行。 當處理器選中一個就緒線程執行它時,該線程進入運行態。通常這意味着某個其他線程被阻塞或者被時間片機制搶佔,處理器會保存被阻塞(或搶佔)線程的環境並恢復下二個就緒線程的環境。 主線程在調用 pthread_join
進入阻塞狀態,等待它創建的線程運行結束。 當調用 pthread_exit
退出線程或調用 pthread_cancel
取消線程時, 線程在調用完清理過程後也將進入終止態。而主線程等到創建的線程終止後重新運行直到結束。
pthread_detach
與pthread_join
用法與差異
- pthread有兩種狀態joinable狀態和unjoinable狀態,如果線程是joinable狀態,當線程函數自己返回退出時或pthread_exit時都不會釋放線程所佔用堆棧和線程描述符(總計8K多)。只有當你調用了pthread_join之後這些資源纔會被釋放。若是unjoinable狀態的線程,這些資源在線程函數退出時或pthread_exit時自動會被釋放。
- unjoinable屬性可以在pthread_create時指定,或在線程創建後在線程中pthread_detach自己, 如:pthread_detach(pthread_self()),將狀態改爲unjoinable狀態,確保資源的釋放。或者將線程置爲 joinable,然後適時調用pthread_join.
- 其實簡單的說就是在線程函數頭加上 pthread_detach(pthread_self())的話,線程狀態改變,在函數尾部直接 pthread_exit線程就會自動退出。省去了給線程擦屁股的麻煩。
pthread_join實例
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
void *thread_function(void *arg)
{
int i;
for ( i=0; i<8; i++)
{
printf("Thread working...! %d \n",i);
sleep(1);
}
return NULL;
}
int main(void)
{
pthread_t mythread;
if ( pthread_create( &mythread, NULL, thread_function, NULL) )
{
printf("error creating thread.");
abort();
}
if ( pthread_join ( mythread, NULL ) )
{
printf("error join thread.");
abort();
}
printf("thread done! \n");
exit(0);
}
運行結果如下:
bspserver@ubuntu:~/workspace/bin$ ./thread_join
Thread working...! 0
Thread working...! 1
Thread working...! 2
Thread working...! 3
Thread working...! 4
Thread working...! 5
Thread working...! 6
Thread working...! 7
thread done!
去掉pthread_join ( mythread, NULL )後再看運行結果如下:
bspserver@ubuntu:~/workspace/bin$ ./thread_join
thread done!
pthread_detach實例
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *tfn(void *arg)
{
pthread_t tid=pthread_self();
int n = 3;
//pthread_detach(pthread_self());
while (n--) {
printf("thread pid %lx count %d\n", tid,n);
sleep(1);
}
pthread_exit((void *)1);
}
int main(void)
{
pthread_t tid;
void *tret;
int err;
#if 0
pthread_attr_t attr; /*通過線程屬性來設置遊離態(分離態)*/
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, tfn, NULL);
#else
pthread_create(&tid, NULL, tfn, NULL);
printf("child thread pid %lx\n",tid);
pthread_detach(tid); //讓線程分離 ----自動退出,無系統殘留資源
#endif
while (1) {
err = pthread_join(tid, &tret);
printf("-------------err= %d\n", err);
if (err != 0)
fprintf(stderr, "thread_join error: %s\n", strerror(err));
else
fprintf(stderr, "thread exit code %d\n", (int)tret);
sleep(1);
}
return 0;
}
運行結果如下:
bspserver@ubuntu:~/workspace/bin$ ./thread_detach
child thread pid 7f3f85671700
-------------err= 22
thread_join error: Invalid argument
thread pid 7f3f85671700 count 2
thread pid 7f3f85671700 count 1
-------------err= 22
thread_join error: Invalid argument
-------------err= 22
thread_join error: Invalid argument
thread pid 7f3f85671700 count 0
-------------err= 22
thread_join error: Invalid argument
-------------err= 22
thread_join error: Invalid argument
-------------err= 22
thread_join error: Invalid argument
-------------err= 22
分析:
- 使用pthread_detach函數實現線程分離時,應當先創建線程(pthread_create),然後再用pthread_detach實現該線程的分離。因此,這種方式與修改線程屬性來實現線程分離的方法相比,不會發生在線程創建函數還未來得及返回時子線程提前結束導致返回的線程號是錯誤的線程號的情況。因爲採用這種方法,即使子線程提前結束(先於pthread_create返回),但是子線程還未處於分離狀態,因此其PCB的殘留信息依然存在,如線程號等一些系統資源,所以線程號等系統資源仍被佔據,還未分配出去,所以創建函數返回的線程號依然是該線程的線程號;
- 不能對一個已經處於detach狀態的線程調用pthread_join進行回收,會出現錯誤,且錯誤編號爲22;
- 還可採用修改線程屬性的方法來實現線程分離。
線程屬性設置
線程屬性
線程具有屬性,用pthread_attr_t表示,在對該結構進行處理之前必須進行初始化,在使用後需要對其去除初始化。 調用pthread_attr_init之後,pthread_t結構所包含的內容就是操作系統實現支持的線程所有屬性的默認值。 如果要去除對pthread_attr_t結構的初始化,可以調用pthread_attr_destroy函數。如果pthread_attr_init實現時爲屬性對象分配了動態內存空間,pthread_attr_destroy還會用無效的值初始化屬性對象,因此如果經pthread_attr_destroy去除初始化之後的pthread_attr_t結構被pthread_create函數調用,將會導致其返回錯誤。
typedef struct
{
int detachstate; // 線程的分離狀態
int schedpolicy; // 線程調度策略
structsched_param schedparam; // 線程的調度參數
int inheritsched; // 線程的繼承性
int scope; // 線程的作用域
size_t guardsize; // 線程棧末尾的警戒緩衝區大小
int stackaddr_set; // 線程的棧設置
void* stackaddr; // 線程棧的位置
size_t stacksize; // 線程棧的大小
} pthread_attr_t;
Posix線程中的線程屬性pthread_attr_t主要包括分離屬性、調度策略屬性、調度策略優先級設置、繼承屬性、堆棧地址、scope屬性、堆棧大小。在pthread_create中,把第二個參數設置爲NULL的話,將採用默認的屬性配置。
- 分離屬性:detachstate,如果設置爲PTHREAD_CREATE_DETACHED 則新線程不能用pthread_join()來同步,且在退出時自行釋放所佔用的資源。缺省爲PTHREAD_CREATE_JOINABLE狀態。這個屬性也可以在線程創建並運行以後用pthread_detach()來設置,而一旦設置爲PTHREAD_CREATE_DETACH狀態(不論是創建時設置還是運行時設置)則不能再恢復到PTHREAD_CREATE_JOINABLE狀態。使用pthread_attr_setdetachstate()設置
- 調度屬性:schedpolicy,表示新線程的調度策略,主要包括SCHED_OTHER(正常、非實時)、SCHED_RR(實時、輪轉法)和SCHED_FIFO(實時、先入先出)三種,缺省爲SCHED_OTHER,後兩種調度策略僅對超級用戶有效。運行時可以用過pthread_attr_setschedpolicy ()來改變。
- 調度策略優先級設置:schedparam,一個struct sched_param結構,目前僅有一個sched_priority整型變量表示線程的優先級。這個參數僅當調度策略爲實時(即SCHED_RR或SCHED_FIFO)時纔有效,並可以在運行時通過pthread_setschedparam()函數來改變,缺省爲0。
- 繼承屬性:inheritsched,有兩種值可供選擇:PTHREAD_EXPLICIT_SCHED和PTHREAD_INHERIT_SCHED,前者表示新線程使用顯式指定調度策略和調度參數(即attr中的值),而後者表示繼承調用者線程的值。通過pthread_attr_setinheritsched()設置,缺省爲PTHREAD_EXPLICIT_SCHED
- scope屬性:scope,表示線程間競爭CPU的範圍,也就是說線程優先級的有效範圍。POSIX的標準中定義了兩個值:PTHREAD_SCOPE_SYSTEM和PTHREAD_SCOPE_PROCESS,前者表示與系統中所有線程一起競爭CPU時間,後者表示僅與同進程中的線程競爭CPU。通過pthread_attr_setscope()設置
設置線程屬性流程如下:
pthread_attr_t attr;
pthread_attr_init(&attr); //線程屬性初始化配置系統默認線程屬性
... /* set up the pthread_attr_t structure */ //設置將要設置的線程屬性
pthread_create (&tid, &attr, &func, &arg);
設置線程屬性函數如下:
//– initializing, destroying
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destroy(pthread_attr_t *attr);
//– setting it up
int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate);
int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize);
int pthread_attr_setinheritsched(pthread_attr_t *attr, int inheritsched);
int pthread_attr_setschedparam(pthread_attr_t *attr, const struct sched_param *param);
int pthread_attr_setschedpolicy(pthread_attr_t *attr, int policy);
int pthread_attr_setscope(pthread_attr_t *attr, int contentionscope);
int pthread_attr_setstackaddr(pthread_attr_t *attr, void *stackaddr);
int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize);
設置線程調度算法和優先級流程如下:
//– setting both:
struct sched_param param;
pthread_attr_setinheritsched (&attr, PTHREAD_EXPLICIT_SCHED);
param.sched_priority = 15;
pthread_attr_setschedparam (&attr, ¶m);
pthread_attr_setschedpolicy (&attr, SCHED_RR);
pthread_create (NULL, &attr, func, arg);
//– setting priority only:
struct sched_param param;
pthread_attr_setinheritsched (&attr, PTHREAD_EXPLICIT_SCHED);
param.sched_priority = 15;
pthread_attr_setschedparam (&attr, ¶m);
pthread_attr_setschedpolicy (&attr, SCHED_NOCHANGE);
pthread_create (NULL, &attr, func, arg);
涉及線程的繼承、調度、參數實例如下:
#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <sched.h>
void *child_thread(void *arg)
{
int policy = 0;
int max_priority = 0,min_priority = 0;
struct sched_param param;
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setinheritsched(&attr,PTHREAD_EXPLICIT_SCHED);
pthread_attr_getinheritsched(&attr,&policy);
if(policy == PTHREAD_EXPLICIT_SCHED){
printf("Inheritsched:PTHREAD_EXPLICIT_SCHED\n");
}
if(policy == PTHREAD_INHERIT_SCHED){
printf("Inheritsched:PTHREAD_INHERIT_SCHED\n");
}
pthread_attr_setschedpolicy(&attr,SCHED_RR);
pthread_attr_getschedpolicy(&attr,&policy);
if(policy == SCHED_FIFO){
printf("Schedpolicy:SCHED_FIFO\n");
}
if(policy == SCHED_RR){
printf("Schedpolicy:SCHED_RR\n");
}
if(policy == SCHED_OTHER){
printf("Schedpolicy:SCHED_OTHER\n");
}
max_priority = sched_get_priority_max(policy);
min_priority = sched_get_priority_min(policy);
printf("Maxpriority:%u\n",max_priority);
printf("Minpriority:%u\n",min_priority);
param.sched_priority = max_priority;
pthread_attr_setschedparam(&attr,¶m);
printf("sched_priority:%u\n",param.sched_priority);
pthread_attr_destroy(&attr);
}
int main(int argc,char *argv[ ])
{
pthread_t child_thread_id;
pthread_create(&child_thread_id,NULL,child_thread,NULL);
pthread_join(child_thread_id,NULL);
return 0;
}
運行結果如下:
bspserver@ubuntu:~/workspace/bin$ ./thread_attr_shced
Inheritsched:PTHREAD_EXPLICIT_SCHED
Schedpolicy:SCHED_RR
Maxpriority:99
Minpriority:1
sched_priority:99
配置線程棧流程如下:
// – to set the maximum size:
pthread_attr_setstacksize (&attr, size);
// – to provide your own buffer for the stack:
pthread_attr_setstackaddr (&attr, addr);
// Thread stack allocation can be automatic:
size = 0; // default size
addr = NULL; // OS allocates
// partly automatic:
size = desired_size;
addr = NULL; // OS allocates
// or totally manual:
size = sizeof (*stack_ptr);
addr = stack_ptr;
// Your stack size should be the sum of:
PTHREAD_STACK_MIN + platform_required_amount_for_code;
線程棧配置實例如下:
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <sys/time.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <sys/prctl.h>
#define DBG_PRINT(fmt, args...) {printf("%s ", __FUNCTION__);printf(fmt,##args);}
#define BUFFER_LEN 0x3000
void *testThead1(void* arg)
{
//設定線程名爲zoobiTask1
prctl(PR_SET_NAME, "zoobiTask1");
char buffer[BUFFER_LEN];
DBG_PRINT("Start\n");
sleep(3);
DBG_PRINT("End\n");
exit(0);
}
#define THREAD_STACK_LEN 0x4000
int main(int argc, const char* argv[])
{
pthread_t thread1ID;
pthread_attr_t attr;
int ret = 0;
void *stackAddr = NULL;
//獲取linux的頁大小
int paseSize = getpagesize();
DBG_PRINT("The linux page size:0x%x\n", paseSize);
pthread_attr_init(&attr);
/**
* 申請內存,並且內存以頁大小對齊,需要申請的內存大小必須是2的整數次冪;
* 經常用的malloc申請的內存只會默認使用8bytes或者16bytes對齊(依賴平臺是32位還是64位);
*/
ret = posix_memalign(&stackAddr, paseSize, THREAD_STACK_LEN);
if(0 != ret)
{
DBG_PRINT("posix_memalign failed, errno:%s\n", strerror(ret));
return -1;
}
#if 1
/**
* 設定線程運行棧地址和大小,棧大小最小爲16KB,並且棧地址以頁面對齊;
*/
ret = pthread_attr_setstack(&attr, stackAddr, THREAD_STACK_LEN);
if(0 != ret)
{
DBG_PRINT("pthread_attr_setstack failed, errno:%s\n", strerror(ret));
return -1;
}
#endif
void *getstackaddr = NULL;
size_t getstackSize = 0;
pthread_attr_getstack(&attr, &getstackaddr, &getstackSize);
DBG_PRINT("getstackaddr:%p, getstackSize:0x%x\n", getstackaddr, getstackSize);
ret = pthread_create(&thread1ID, &attr, testThead1, NULL);
if(ret != 0)
{
DBG_PRINT("pthread_create failed! errno:%s\n", strerror(ret));
return -1;
}
pthread_detach(thread1ID);
sleep(5);
printf("thread done! \n");
return 0;
}
運行結果如下:
bspserver@ubuntu:~/workspace/bin$ ./pthread_stack
main The linux page size:0x1000
main getstackaddr:0x560499a25000, getstackSize:0x4000
testThead1 Start
testThead1 End
如果將BUFFER_LEN更改爲0x4000,如下打印:
bspserver@ubuntu:~/workspace/bin$ ./pthread_stack
main The linux page size:0x1000
main getstackaddr:0x55c4d2e40000, getstackSize:0x4000
Segmentation fault (core dumped)
局部變量過大導致棧已經溢出出現Segmentation fault (core dumped)
錯誤。
參考文獻:
linux中pthread_join()與pthread_detach()詳解
設定線程運行棧:pthread_attr_setstack()