1. 程式人生 > >Linux學習之多執行緒程式設計(四)

Linux學習之多執行緒程式設計(四)

言之者無罪,聞之者足以戒。 ——《詩序》

三、Linux執行緒的高階控制

1、一次性初始化

有些事需要且只能執行一次(比如互斥量初始化)。通常當初始化應用程式時,可以比較容易地將其放在main函式中。但當你寫一個庫函式時,就不能在main裡面初始化了,你可以用靜態初始化,但使用一次初始(pthread_once_t)會比較容易些。

首先要定義一個pthread_once_t變數,這個變數要用巨集PTHREAD_ONCE_INIT初始化。然後建立一個與控制變數相關的初始化函式。pthread_once_t once_control = PTHREAD_ONCE_INIT;

void init_routine() {  //初始化互斥量  //初始化讀寫鎖  ...... }

接下來就可以在任何時刻呼叫pthread_once函式

(1)、pthread_once一次性初始化函式

int pthread_once(pthread_once_t* once_control, void (*init_routine)(void))

第一個引數:once_control(初始化函式呼叫情況的標誌)

第二個引數:初始化執行函式

返回值:成功返回0,失敗返回錯誤碼

功能:此函式使用初值為PTHREAD_ONCE_INIT的once_control變數保證init_routine()函式在本程序執行序列中僅執行一次。在多執行緒程式設計環境下,儘管pthread_once()呼叫會出現在多個執行緒中,init_routine()函式僅執行一次,究竟在哪個執行緒中執行是不定的,是由核心排程來決定。

Linux Threads使用互斥鎖和條件變數保證由pthread_once()指定的函式執行且僅執行一次。實際"一次性函式"的執行狀態有三種:

NEVER(0)、IN_PROGRESS(1)、DONE (2),用once_control來表示pthread_once()的執行狀態:

1)、如果once_control初值為0,那麼 pthread_once從未執行過,init_routine()函式會執行。

2)、如果once_control初值設為1,則由於所有pthread_once()都必須等待其中一個激發"已執行一次"訊號, 因此所有pthread_once ()都會陷入永久的等待中,init_routine()就無法執行

3)、如果once_control設為2,則表示pthread_once()函式已執行過一次,從而所有pthread_once()都會立即  返回,init_routine()就沒有機會執行

當pthread_once函式成功返回,once_control就會被設定為2

直接給出程式碼:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

pthread_t tid;
pthread_once_t once=PTHREAD_ONCE_INIT;

void thread_init()
{
        printf("I am in thread 0x%x\n",tid);
}

void *thread_fun1(void *arg)
{
        tid = pthread_self();
        printf("I am thread 0x%x\n",tid);
        printf("once is %d\n",once);
        pthread_once(&once,thread_init);
        printf("once is %d\n",once);

        return NULL;
}
void *thread_fun2(void *arg)
{
        sleep(2);
        tid = pthread_self();
        printf("I am thread 0x%x\n",tid);
//      printf("once is %d\n",once);
        pthread_once(&once,thread_init);
//      printf("once is %d\n",once);

        return NULL;
}


int main()
{
        pthread_t tid1, tid2;
        int err;

        err = pthread_create(&tid1, NULL, thread_fun1, NULL);
        if(err != 0)
        {
                printf("create new thread 1 failed\n");
                return ;
        }
        err = pthread_create(&tid2, NULL, thread_fun2, NULL);
        if(err != 0)
        {
                printf("create new thread 1 failed\n");
                return ;
        }


        pthread_join(tid1, NULL);
        pthread_join(tid2, NULL);

        return 0;
}

2、執行緒的屬性

執行緒的屬性用pthread_attr_t型別的結構表示,在建立執行緒的時候可以不用傳入NULL,而是傳入一個pthread_attr_t結構,有使用者自己來配置執行緒的屬性。pthread_attr_t型別對應用程式是不透明的,也就是說應用程式不需要了解有關屬性物件內部結構的任何細節,因而可以增加程式的可移植性。

執行緒屬性:

名稱 描述
detachstate 執行緒的分離狀態
guardsize 執行緒棧末尾的警戒區域大小(位元組數)
 stackaddr 執行緒棧的最低地址
stacksize 執行緒棧的大小(位元組數)

並不是所有的系統都支援執行緒的這些屬性,因此你需要檢查當前系統是否支援你設定的屬性。當然還有一些屬性不包含在pthread_attr_t結構中,例如:執行緒的可取消狀態、取消型別、併發度

(1)、pthread_attr_init執行緒屬性初始化函式

int pthread_attr_init(pthread_attr_t *attr)

引數:要初始化的屬性

返回值:成功返回0 ,失敗返回錯誤碼

(2)、pthread_attr_destory執行緒屬性銷燬函式

int pthread_attr_destroy(pthread_attr_t *attr)

引數:要初始化的屬性

返回值:成功返回0 ,失敗返回錯誤碼

如果在呼叫pthread_attr_init初始化屬性的時候分配了記憶體空間,那麼pthread_attr_destroy將釋放記憶體空間。除此之外,pthread_atty_destroy還會用無效的值初始化pthread_attr_t物件,因此如果該屬性物件被誤用,會導致建立執行緒失敗。

注意:pthread_attr_t結構在使用之前需要初始化,使用完之後需要銷燬

什麼叫做分離?

分離一個正在執行的執行緒並不影響它,僅僅是通知當前系統該執行緒結束時,其所屬的資源可以回收。一個沒有被分離的執行緒在終止時會保留它的虛擬記憶體,包括他們的堆疊和其他系統資源,有時這種執行緒被稱為“殭屍執行緒”。建立執行緒時預設是非分離的。如果執行緒具有分離屬性,執行緒終止時會被立刻回收,回收將釋放掉所有線上程終止時未釋放的系統資源和程序資源,包括儲存執行緒返回值的記憶體空間、堆疊、儲存暫存器的記憶體空間等。

(3)、pthread_attr_setdetachstate設定執行緒的分離屬性

int pthread_attr_setdetachstate(pthread_attr_t *attr, int detachstate)

第一個引數:要設定的分離狀態屬性

第二個引數:狀態設定(有兩種值:PTHREAD_CREATE_DETACHED分離的、PTHREAD_CREATE_JOINABLE 非分離的,可連線的)

返回值:成功返回0 ,失敗返回錯誤碼

(4)、pthread_attr_getdetachstate分離狀態屬性獲取函式

 int pthread_attr_getdetachstate(pthread_attr_t *attr, int *detachstate)

第一個引數:要設定的分離狀態屬性

第二個引數:已經設定的分離狀態屬性

返回值:成功返回0 ,失敗返回錯誤碼

設定執行緒分離屬性的步驟 1、定義執行緒屬性變數pthread_attr_t attr 2、初始化attr,pthread_attr_init(&attr) 3、設定執行緒為分離或非分離 pthread_attr_setdetachstate(&attr, detachstate) 4、建立執行緒pthread_create(&tid, &attr, thread_fun,  NULL) 所有的系統都會支援執行緒的分離狀態屬性,

下面看一下程式框圖:

程式程式碼:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>


void *thread_fun1(void *arg)
{

        printf("I'm new thread 1\n");

        return (void *)1;
}

void *thread_fun2(void *arg)
{
        printf("I'm new thread 2\n");

        return (void *)2;
}

int main()
{
        pthread_t tid1, tid2;
        int err;
        int *flag;
        //定義屬性變數
        pthread_attr_t attr;
        //初始化屬性
        pthread_attr_init(&attr);
        //設定分離狀態屬性,置為已分離
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
        //獲取分離狀態的屬性    
        pthread_attr_getdetachstate(&attr,flag );
        printf("flag is %p\n",flag);

        err = pthread_create(&tid1, &attr, thread_fun1, NULL);
        if(err)
        {
                printf("create new thread 1 failed\n");
                return;
        }

        err = pthread_create(&tid2, NULL, thread_fun2, NULL);
        if(err)
        {
                printf("create new thread 2 failed\n");
                return;
        }

        //連線執行緒1
        err = pthread_join(tid1, NULL);
        if(!err)
                printf("join thread 1 success\n");
        else
                printf("join thread 1 failed\n");
        //連線執行緒2
        err = pthread_join(tid2, NULL);
        if(!err)
                printf("join thread 2 success\n");
        else
                printf("join thread 2 failed\n");

        //銷燬attr
        pthread_attr_destroy(&attr);


        return 0;
}

對於程序來說,虛擬地址空間的大小是固定的,程序中只有一個棧,因此它的大小通常不是問題。但對執行緒來說,同樣的虛擬地址被所有的執行緒共享。如果應用程式使用了太多的執行緒,致使執行緒棧累計超過可用的虛擬地址空間,這個時候就需要減少執行緒預設的棧大小。另外,如果執行緒分配了大量的自動變數或者執行緒的棧幀太深,那麼這個時候需要的棧要比預設的大。如果用完了虛擬地址空間,可以使用malloc或者mmap來為其他棧分配空間,並修改棧的位置。

(5)、pthread_attr_setstack修改棧的屬性

int pthread_attr_setstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize)

第一個引數:棧屬性

第二個引數:棧的記憶體單元最低地址

第三個引數:棧的大小

返回值:成功返回0,失敗返回錯誤碼

(6)、phread_attr_getstack獲取棧屬性

int pthread_attr_getstack(pthread_attr_t *attr, void *stackaddr, size_t stacksize)

第一個引數:棧屬性

第二個引數:棧的記憶體單元最低地址

第三個引數:棧的大小

返回值:成功返回0,失敗返回錯誤碼

引數stackaddr是棧的記憶體單元最低地址,引數stacksize是棧的大小。要注意stackaddr並不一定是棧的開始,對於一些處理器,棧的地址是從高往低的,那麼這是stackaddr是棧的結尾。

當然也可以單獨獲取或者修改棧的大小,而不去修改棧的地址。對於棧大小設定,不能小於PTHREAD_STACK_MIN(需要標頭檔案limit.h)

(7)、pthread_attr_setstacksize設定棧的大小

int pthread_attr_setstacksize(pthread_attr_t *attr, size_t stacksize)

第一個引數:棧屬性

第二個引數:要設定的棧大小

返回值:成功返回0,失敗返回錯誤碼

(8)、pthread_attr_getstacksize獲取棧的大小

int pthread_attr_getstacksize(pthread_attr_t *attr, size_t *stacksize)

第一個引數:棧屬性

第二個引數:要獲取的棧大小

返回值:成功返回0,失敗返回錯誤碼

對於棧大小的設定,在建立執行緒之後,還可以修改。

對於遵循POSIX標準的系統來說,不一定要支援執行緒的棧屬性,因此你需要檢查

1)、在編譯階段使用

_POSIX_THREAD_ATTR_STACKADDR 和 _POSIX_THREAD_ATTR_STACKSIZE符號來檢查系統

_POSIX_THREAD_ATTR_STACKSIZE符號來檢查系統在/usr/include/bits/posix_opt.h(可以用#ifdef和#endif來實現)

2)、在執行階段

把_SC_THREAD_ATTR_STACKADD和 _SC_THREAD_THREAD_ATTR_STACKSIZE傳遞給sysconf函式檢查系統對執行緒棧屬性的支援

執行緒屬性guardsize控制著執行緒棧末尾以後用以避免棧溢位的擴充套件記憶體的大小,這個屬性預設是PAGESIZE個位元組。你可以把它設為0,這樣就不會提供警戒緩衝區。同樣的,如果你修改了stackaddr,系統會認為你自己要管理棧,警戒緩衝區會無效。

(9)、pthread_attr_setguardsize設定設定guardsize

int pthread_attr_setguardsize(pthread_attr_t *attr, size_t guardsize)

第一個引數:棧屬性

第二個引數:擴充套件記憶體的大小

返回值:成功返回0,失敗返回錯誤碼

(10)、pthread_attr_getguardsize獲取guardsize

int pthread_attr_getguardsize(pthread_attr_t *attr, size_t *guardsize)

第一個引數:棧屬性

第二個引數:擴充套件記憶體的大小

返回值:成功返回0,失敗返回錯誤碼

下面來看一下程式,熟悉一下上面說的函式:

#include <pthread.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <errno.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>

pthread_attr_t attr;
void *thread_fun(void *arg)
{
        size_t stacksize;
#ifdef _POSIX_THREAD_ATTR_STACKSIZE
        pthread_attr_getstacksize(&attr,&stacksize);
        printf("new thread stack size is %d\n",stacksize);
        pthread_attr_setstacksize(&attr,17684);
        pthread_attr_getstacksize(&attr,&stacksize);
        printf("new thread stack size is %d\n",stacksize);
#endif
        return (void *)1;
}
int main()
{
        pthread_t tid;
        int err;
        pthread_attr_init(&attr);
        pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);

#ifdef _POSIX_THREAD_ATTR_STACKSIZE
        pthread_attr_setstacksize(&attr, PTHREAD_STACK_MIN);
#endif
        err = pthread_create(&tid, &attr, thread_fun, NULL);
        if(err)
        {
                printf("create new thread failed\n");
                return;
        }

        pthread_join(tid,NULL);
        return 0;
}