執行緒概述、執行緒控制和執行緒私有資料
一、執行緒概述
在許多經典的作業系統教科書中,總是把程序定義為程式的執行例項,它並不執行什麼, 只是維護應用程式所需的各種資源,而執行緒則是真正的執行實體。在一個程序中的多個執行路線叫做執行緒。為了讓程序完成一定的工作,程序必須至少包含一個執行緒。 執行緒又叫輕量級程序(LWP)。
執行緒會共享程序的一些資源,也有一些資源是執行緒獨立擁有的。但是不同程序的執行緒是不共享資源的。
【共享】: 程式碼區、資料區、堆區(注意沒有棧區)、環境變數和命令列引數、檔案描述符、訊號處理函式、當前目錄、使用者 ID 和組 ID 等。
【非共享】:
ID、暫存器值、棧記憶體、排程策略和優先順序、訊號掩碼、errno變數以及執行緒私有資料等。
也可以說執行緒是包含在程序中的一種實體。它有自己的執行線索,可完成特定任務。可與其他執行緒共享程序中的共享變數及部分環境。可通過相互之間協同來完成程序所要完成的任務。
二、執行緒控制
1、執行緒標識
就像每個程序都有一個程序號一樣,每個執行緒也有一個執行緒號。程序號在整個系統中是唯一的,但執行緒號不同,執行緒號只在它所屬的程序環境中有效。程序號用 pid_t 資料型別表示,是一個非負整數。執行緒號則用 pthread_t 資料型別來表示,Linux 使用無符號長整數表示。有的系統在實現 pthread_t 的時候,用一個結構體來表示,所以在可移植的作業系統實現不能把它做為整數處理。
(1)獲取執行緒號
#include <pthread.h>
pthread_t pthread_self(void);
- 功能: 獲取執行緒號。
- 引數: 無。
- 返回值: 呼叫執行緒的執行緒 ID 。
(2)執行緒號的比較
#include <pthread.h>
int pthread_equal(pthread_t t1, pthread_t t2);
- 功能: 判斷執行緒號 t1 和 t2 是否相等。為了方便移植,儘量使用函式來比較執行緒 ID。
- 引數: t1,t2:待判斷的執行緒號。
- 返回值: 相等:非 0;不相等:0。
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
int main()
{
pthread_t thread_id;
thread_id = pthread_self(); // 獲取執行緒號
printf("thread id = %lu\n", thread_id);
if (pthread_equal(thread_id, pthread_self())) // 執行緒號比較
printf("Equal\n");
else
printf("not Equal\n");
return 0;
}
// 執行緒函式的程式在 pthread 庫中,故連結時要加上引數 -lpthread。
2、執行緒建立
#include <pthread.h>
int pthread_create( pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine)(void *), void *arg );
- 功能: 建立一個執行緒。
- 引數:
- thread:執行緒識別符號地址;
- attr:執行緒屬性結構體地址,通常設定為 NULL;
- start_routine:執行緒函式的入口地址;
- arg:傳給執行緒函式的引數。
- 返回值: 成功:0;失敗:非0。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
void *fun(void *arg)
{
int *p = (int *)arg;
printf("child thread pid = %d\n", getpid());
printf("child thread id = %lu\n", pthread_self());
printf("child thread *arg = %d\n", *p);
sleep(1);
}
int main()
{
pthread_t tid;
int n = 10;
// 建立一個執行緒,fun是執行緒函式,n是傳給執行緒函式的引數,
// 強制轉換為void *型別。
int err = pthread_create(&tid, NULL, fun, (void *)&n);
if (err != 0)
{
// 如果建立失敗,打印出錯資訊。
fprintf(stderr, "can't create thread:%s\n", strerror(err));
exit(1);
}
printf("main pid = %d\n", getpid());
printf("main thread id = %lu\n", pthread_self());
printf("main child thread id = %lu\n", tid);
// 執行緒建立時並不能保證哪個執行緒先執行:是新建的執行緒還是呼叫執行緒。
sleep(2);
return 0;
}
3、執行緒終止
(1)執行緒退出
#include <pthread.h>
void pthread_exit(void *retval);
- 功能: 主要用於終止正在執行的執行緒,但並不釋放資源。
- 引數:
- retval:來帶出執行緒的退出狀態資訊。
- 返回值: 無。
線上程過程函式或者被執行緒過程函式直接或間接呼叫的函式中,呼叫 pthread_exit 函式,其效果都與線上程過程函式中執行 return 語句效果一樣 – 終止呼叫執行緒。注意,在任何執行緒中呼叫 exit 函式,被終止的都是程序。當然隨著程序的終止,隸屬於該程序的包括呼叫執行緒在內的所有執行緒也都一併終止。
【Note】: 如果thread執行緒函式從return返回,則retval存放的是thread執行緒函式的返回值;如果從pthread_exit返回,則retval存放的是pthread_exit的引數。
(2)執行緒取消
#include <pthread.h>
int pthread_cancel(pthread_t thread);
- 功能: 對引數指定的執行緒傳送取消的請求(必須是同一程序中的其他執行緒)。
- 引數:
- thread:執行緒號。
- 返回值: 成功:0;失敗:錯誤碼。
該函式只是向執行緒發出取消請求,並不等於執行緒終止。預設情況下,執行緒在收到取消請求以後,並不會立即終止,而是仍繼續執行,直到其達到某個取消點。在取消點處,執行緒檢查其自身是否已被取消,若是則立即終止。當執行緒呼叫一些特定函式時,取消點會出現。
(3)執行緒回收(阻塞)
#include <pthread.h>
int pthread_join(pthread_t thread, void **retval);
- 功能: 等待執行緒結束(此函式會阻塞),並回收執行緒資源,類似程序的 wait() 函式。如果執行緒已經結束,那麼該函式會立即返回。
- 引數:
- thread:被等待的執行緒號;
- retval:用來儲存執行緒退出狀態的指標的地址。
- 返回值: 成功:0;失敗:非0。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>
void *thrfun1(void *p)
{
int sum = 0;
for (int i = 1; i <= 10; ++i)
sum += i;
printf("子執行緒1退出\n");
return (void *)sum; // 為了保持型別匹配,強制轉換。
}
void *thrfun2(void *p)
{
printf("子執行緒2退出\n");
pthread_exit((void *)2); // 本執行緒退出,不影響其他執行緒。
}
void *thrfun3(void *p)
{
while (1)
{
printf("子執行緒3執行\n");
sleep(1);
}
}
int main()
{
pthread_t tid;
void *it;
pthread_create(&tid, NULL, thrfun1, NULL);
pthread_join(tid, &it);
printf("子執行緒1返回的資料是:%ld\n", (long)it);
pthread_create(&tid, NULL, thrfun2, NULL);
pthread_join(tid, &it);
printf("子執行緒2返回的資料是:%ld\n", (long)it);
pthread_create(&tid, NULL, thrfun3, NULL);
sleep(3); // 主控執行緒執行三秒後取消子執行緒3
pthread_cancel(tid); // 執行緒被取消
// 回收由pthread_cancel終止的程序,返回值是一個巨集(-1)
pthread_join(tid, &it);
printf("子執行緒3返回的資料是:%ld\n", (long)it);
return 0;
}
(4)執行緒回收(非阻塞)
#include <pthread.h>
int pthread_detach(pthread_t thread);
- 功能: 主要用於將引數指定的執行緒標記為分離狀態。
- 引數:
- thread:執行緒號;
- 返回值: 成功:0;失敗:錯誤碼。
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
void *thrfun(void *p)
{
int n = 3;
while (n--)
{
printf("thread count %d\n", n);
sleep(1);
}
return (void *)1;
}
int main()
{
pthread_t tid;
void *it;
int err;
pthread_create(&tid, NULL, thrfun, NULL);
// 如果沒有pthread_detach,主控執行緒會阻塞回收子執行緒,
// 並且列印返回狀態和出錯碼。
// 如果設定了pthread_detach,主控執行緒非阻塞回收子執行緒,
// 這種情況下再呼叫pthread_join會出錯。
pthread_detach(tid);
while (1)
{
err = pthread_join(tid, &it);
if (err != 0)
fprintf(stderr, "thread %s\n", strerror(err));
else
fprintf(stderr, "thread exit code %ld\n", (long)it);
sleep(1);
}
return 0;
}
對於分離狀態的執行緒來說:當該執行緒終止後,會自動將資源釋放給系統,不需要其他執行緒的加入/等待(即:立即回收資源),也就是說分離的執行緒無法被其他執行緒使用 pthread_join 進行等待。建議:對於新啟動的執行緒來說,要麼使用 pthread_detach 設定為分離狀態,要麼使用 pthread_join 設定為可加狀態(即:pthread_join 和 pthread_detach 是互斥的)。
三、執行緒私有資料
有時應用程式設計中必要提供執行緒私有的全域性變數,這個變數僅線上程中有效,但卻可以跨過多個函式訪問。比如在程式裡可能需要每個執行緒維護一個連結串列,而會使用相同的函式來操作這個連結串列,最簡單的方法就是使用同名而不同變數地址的執行緒相關資料結構。這樣的資料結構可以由 Posix 執行緒庫維護,成為執行緒私有資料 (Thread-specific Data,或稱為TSD)。
1、建立執行緒私有資料
#include <pthread.h>
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));
- 功能: 建立一個型別為 pthread_key_t 型別的私有資料變數( key )。
- 引數:
- key:在分配( malloc )執行緒私有資料之前,需要建立和執行緒私有資料相關聯的鍵( key ),這個鍵的功能是獲得對執行緒私有資料的訪問權;
- destructor:清理函式名字( 如:fun )。當執行緒退出時,如果執行緒私有資料地址不是非 NULL,此函式會自動被呼叫。該函式指標可以設成 NULL ,這樣系統將呼叫預設的清理函式。
- 返回值: 成功:0;失敗:非0。
不論哪個執行緒呼叫 pthread_key_create(),所建立的 key 都是所有執行緒可訪問,但各個執行緒可根據自己的需要往 key 中填入不同的值,相當於提供了一個同名不同值的變數。
2、登出執行緒私有資料
#include <pthread.h>
int pthread_key_delete(pthread_key_t key);
- 功能: 登出執行緒私有資料。這個函式並不會檢查當前是否有執行緒正使用執行緒私有資料( key ),也不會呼叫清理函式 destructor() ,而只是將執行緒私有資料( key )釋放以供下一次呼叫 pthread_key_create() 使用。
- 引數:
- key:待登出的私有資料。
- 返回值: 成功:0;失敗:非0。
3、設定執行緒私有資料的關聯
#include <pthread.h>
int pthread_setspecific(pthread_key_t key, const void *value);
- 功能: 設定執行緒私有資料( key ) 和 value 關聯,注意,是 value 的值(不是所指的內容)和 key 相關聯。
- 引數:
- key:執行緒私有資料;
- value:和 key 相關聯的指標。
- 返回值: 成功:0;失敗:非0。
4、讀取執行緒私有資料所關聯的值
#include <pthread.h>
void *pthread_getspecific(pthread_key_t key);
- 功能: 讀取執行緒私有資料( key )所關聯的值。
- 引數:
- key:執行緒私有資料;
- 返回值: 成功:執行緒私有資料( key )所關聯的值;失敗:NULL。
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_key_t key; // 私有資料,全域性變數
void echomsg(void *t)
{
printf("[destructor] thread_id = %lu, param = %p\n", pthread_self(), t);
}
void *child1(void *arg)
{
int i = 10;
pthread_t tid = pthread_self(); //執行緒號
printf("\nset key value %d in thread %lu\n", i, tid);
pthread_setspecific(key, &i); // 設定私有資料
printf("thread one sleep 2 until thread two finish\n\n");
sleep(2);
printf("\nthread %lu returns %d, add is %p\n", tid, *((int *)pthread_getspecific(key)), pthread_getspecific(key));
}
void *child2(void *arg)
{
int temp = 20;
pthread_t tid = pthread_self(); //執行緒號
printf("\nset key value %d in thread %lu\n", temp, tid);
pthread_setspecific(key, &temp); //設定私有資料
sleep(1);
printf("thread %lu returns %d, add is %p\n", tid, *((int *)pthread_getspecific(key)), pthread_getspecific(key));
}
int main(void)
{
pthread_t tid1,tid2;
pthread_key_create(&key, echomsg); // 建立
pthread_create(&tid1, NULL, child1, NULL);
pthread_create(&tid2, NULL, child2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_key_delete(key); // 登出
return 0;
}