1. 程式人生 > >linux多執行緒相關的API-(1)--建立/退出/加入/分離等

linux多執行緒相關的API-(1)--建立/退出/加入/分離等

注意:多執行緒相關的程式碼,在編譯時必須加-lpthread或者-pthread選項,例如:

gcc thread_test.c  -o test_exe  -pthread

一、建立執行緒:pthread_create

原型:

int pthread_create(pthread_t *tid,
                    const pthread_attr_t *attr,
                    (void*)(*start_rtn)(void* arg),
                    void *arg);

形參:tid輸出所成功建立的新執行緒的執行緒ID,tid=thread id;

attr設定所建立的執行緒引數,傳入NULL意味著使用預設引數;

start_rtn執行緒入口函式,函式格式形如: void *thread_entry(void *para);

arg建立新執行緒時,要傳給入口函式的引數;

注意:通過本函式把引數arg傳給新執行緒時,是需要一定時間的(確切的說,是建立執行緒這一過程較為耗時),那麼從執行完本函式開始,一直到新執行緒被建立完成,且新執行緒讀完arg引數之前,主執行緒中實參arg指向的內容不允許變,否則,新執行緒讀到的啟動引數就不對了,這一問題主要出現在類似這種程式碼中:

pthread_t  tid[5];
for( i = 0; i < 5; i++)
{
    stat = pthread_create(&tid[i], NULL, test_func, &i);
    ````//其餘程式碼
}

感覺上,建立的5個執行緒,會在各自的入口函式中,依次收到pthread_create的第4個實參:0、1、2、3、4,而實際上,這5個執行緒收到的卻都是5,原因就在於,程序尚未讀到這個第四實參,i的值就被改變了。
這一問題一般有3個解決方法:
① 本函式返回後,sleep一段時間,目的是執行緒建立完畢,且讀完arg引數,
② 為每一個執行緒用不同的變數傳遞引數,而不是跟這個例子似的,用同一個變數i;
③ 這種方法僅適用於傳遞的資料的位元組數≤sizeof(指標)時,換句話說就是,本來傳參是通過傳遞個地址給執行緒,讓執行緒從這個地址中讀出真正的引數,顯然,這也是引發上述問題的根源,這個地址處的值可能會改變。但是,pthread_create傳遞的第4實參,指標本身的值不會變,也即,我們可以投機取巧,把這個指標本身的值作為引數傳給新執行緒,這個值無論如何不會出錯的。也即,線上程入口函式void* start_rtn(void* arg)中,讀*arg的值可能會變,*arg的值可能會被主執行緒給修改,但是arg本身的值,是不會變的。

二、執行緒退出:pthread_exit

原型:void pthread_exit(void* retval);

功能:立即退出執行緒,如果退出時還想返回一些資料供父執行緒處理/使用,那麼這些資料可以通過函式形參retval傳出,傳出的值可通過pthread_join函式獲得。

注意:

1、我們傳出的是資料的地址,而不是資料本身,因此資料本身必須是靜態的(如果多個執行緒都用同一個入口函式,這方法就不行了)、或者從記憶體堆動態分配的,而決不能是函式中的動態臨時變數,因為一旦執行緒入口函式返回,那麼棧中的資料值就消失/不可用了

2、線上程的入口函式中執行return p;和執行pthread_exit(p);相同點是:都可以退出執行緒,都可以傳出返回值,而且返回值都可以被pthread_join捕獲。

區別在於:return p只能在入口函式中退出執行緒,而pthread_exit(p)可以在入口函式的子函式中退出執行緒;return p不會觸發執行緒清理工作,pthread_exit(p)可以觸發執行緒清理工作,詳情請參考另一篇博文:執行緒取消與清理。

三、阻塞地等待某個執行緒退出pthread_join

原型:int pthread_join(pthread_t tid, void **retval);

功能:阻塞地等待某個執行緒退出,另外注意:父執行緒建立子執行緒之後,父執行緒會維護一些子執行緒的資訊,只有顯式地呼叫了該函式,父程序才會在子執行緒結束後清理所維護的子執行緒資訊,換句話說,如果父執行緒不呼叫該函式,那麼大量的子執行緒資訊得不到釋放,可能會造成堆溢位,除非:我們把建立的子執行緒與父執行緒分離開,這樣子執行緒的資訊在子執行緒結束後自動釋放,分離執行緒所用的函式為:pthread_detach(pthread_t tid);

形參:tid 要等待的執行緒id;

retval為執行緒退出時,傳出的值的指標的指標

//編譯:gcc thread_basic.c -o test_exe -pthread
#include <limits.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <stdio.h>
#include <error.h>
#include <errno.h>
#include <sys/types.h>
#include <unistd.h>

void *thread_entry(void* arg)
{
        int arg_val =  *(int*)arg;
        printf("child thread start with para: %d\n", arg_val);

        int *p_out = malloc(sizeof(int));//申請的空間用於傳出執行緒的退出值
        //static int data_out = 0;
        //int *p_out = &data_out;//區域性靜態空間也可用來傳出退出值
        *p_out = arg_val + 1;
        pthread_exit(p_out); //在入口函式中,本行程式碼等價於 return p_out;
        return p_out;//這一行實際上執行不到,執行緒已經被pthread_exit給退出了
}

int main()
{
        int status;//執行緒操作函式的返回值
        pthread_t  tid;//子執行緒ID
        int arg = 2;//傳給子執行緒入口函式的實參
        int *p_recv_out;//pthread_join接收子執行緒的返回值

        //建立子執行緒
        status = pthread_create(&tid, NULL, thread_entry, &arg);
        if(0 == status)
        {
                printf("creat thread ok, tid = %ld\n", tid);
        }
        else
        {
                printf("creat thread failed, error info: %s\n", strerror(errno));
                return -1;
        }

        //等待子執行緒結束,並接收子執行緒的結束值
        status = pthread_join(tid, (void**)&p_recv_out);
         if(0 == status)
         {
                printf("child thread quit normally\n");
                printf("parent thread received exit data: %d\n", *p_recv_out);
                free(p_rec

執行結果: