1. 程式人生 > >POSIX 線程的創建與退出

POSIX 線程的創建與退出

posix 共享 iat immediate 類型 進程 nis hat tin

前言

創建線程:

pthread_create()

退出線程:

pthread_exit()return
pthread_cancel()

線程的創建

使用多線程,首先就需要創建一個新線程。那麽線程是如何被創建的呢,是用下面這個函數創建的。

#include <pthread.h>

int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                   void *(*start_routine) (void *), void *arg);

//Compile and link with -pthread
 

創建函數的四個參數的意義分別如下:

thread :用來返回新創建的線程的 ID,這個 ID 就像身份證一樣,指定了這個線程,可以用來在隨後的線程交互中使用。

attr   : 這個參數是一個 pthread_attr_t 結構體的指針,用來在線程創建的時候指定新線程的屬性。如果在創建線程時,這個參數指定為 NULL, 那麽就會使用默認屬性。

start_routine :這個就是新線程的入口函數,當新線程創建完成後,就從這裏開始執行。

arg :arg 參數就是要傳遞給 start_routine 的參數。

返回值:如果函數執行成功,則返回 0,如果執行失敗,則返回一個錯誤碼。

錯誤碼:

    EAGAIN :資源不足以用來創建一個新的線程,或者是達到了系統對線程數量的限制,請參考 setrlimit(2) 和 /proc/sys/kernel/threads-max 
    EINVAL :不可用的 attr 
    EPERM  :沒有權限設置 attr 中的一下屬性或者執行時序策略。

下面就是調用 pthread_create() 函數創建線程的一個例子:

#include <stdio.h>
#include <pthread.h>
#include <errno.h>

void *
thread_start(void
*arg) { if(NULL == arg) { printf("[%u] : arg is NULL\n", (unsigned int)pthread_self()); return NULL; } char * p = (char*)arg; printf("[%u] : arg = [%s]\n", (unsigned int)pthread_self(), p); return NULL; } int main() { pthread_t pt; int errn = pthread_create( &pt, //用來返回新創建的線程的 ID NULL, //使用默認的線程屬性 thread_start,//新線程從這個函數開始執行 "hello"); //傳遞給新創建的線程的參數 if(0 != errn) { printf("error happend when create pthread, errno = [%d]\n", errn); if(EAGAIN == errn) { printf("Insufficient resources\n"); } else if (EINVAL == errn) { printf("Invalid settings in attr\n"); } else if (EPERM == errn) { printf("No permission\n"); } else { printf("An error number that unexpected [%d], when create pthread\n", errn); } return -1; } else { printf("create thread success, threadid : [%u]\n", (unsigned int)pt); } void *r = NULL; errn = pthread_join(pt, &r); if(0 != errn) { printf("error happend when join, errno = [%d]\n", errn); if(EDEADLK == errn) { printf("A deadlock was detected; or thread specifies the calling thread\n"); } else if (EINVAL == errn) { printf("thread is not a joinable thread, or Another thread is already waiting to join with this thread\n"); } else if (ESRCH == errn) { printf("No thread with the ID thread could be found\n"); } else { printf("An error number that unexpected [%d], when join\n", errn); } return -1; } else { printf("thread [%u] over\n", (unsigned int)pt); } return 0; }

接下來編譯並運行,看看結果:

gcc -g -c -o pthread_create.o pthread_create.c -Wall -I./
gcc -g -o pthread_create pthread_create.o -Wall -I./ -lpthread

create thread success, threadid : [1243236096]
[1243236096] : arg = [hello]
thread [1243236096] over

看起來執行成功了。下面再來看看一個線程的退出過程。

線程的退出

從上面的例子中,我們也可以看出,線程的入口,也就是一個函數,函數可以使用 return 進行退出, 那麽在線程中,也是通過 return 進行退出的嗎? 答案是,可以使用 return ,但是如果希望線程在退出的時候, 能夠執行更多的動作,就不能使用 return 直接退出了,那麽該怎樣退出呢,可以使用 pthread_exit() 函數, 或者使用pthread_cancel() 函數。

這兩個函數的原型如下:

#include <pthread.h>

int pthread_cancel(pthread_t thread);   //向指定的線程發送取消請求
void pthread_exit(void *retval);        //結束調用者線程

//Compile and link with -pthread

使用 pthread_exit() 退出線程

pthread_exit() 函數會結束當前進程。如果當前線程是可以被 join 的,則會通過參數 retval 返回一個值給同一個進程裏面的另一個使用pthread_join(3) 函數的線程。

所有使用pthread_cleanup_push(3)函數壓入棧的清理函數,都會被彈出並調用, 調用順序是入棧時的反向順序。如果線程有什麽特別指定的數據,那麽在所有的清理函數執行結束後, 會有適當的函數被調用,來析構這些數據,調用順序不固定。

當一個線程終止後,進程內共享的資源(例如互斥信號量、條件變量、信號量以及文件描述符) 不會被釋放。並且使用atexit(3)函數註冊的函數也不會被調用。

當進程內的最後一個線程終止後,進程也就終止了,就像調用了exit(3)函數一樣,並且參數是0. 這時候,進程內的共享資源就會被釋放,並且使用 atexit(3) 函數註冊的函數, 也會被調用。

下面來看一下 pthread_exit() 函數的一個例子:

#include <stdio.h>
#include <pthread.h>

void handlers(void *arg) {
    if(NULL != arg) {
        printf("%s() : [%s]\n", __func__, (char*)arg);
    } else {
        printf("%s()\n", __func__);
    }
}

void *
thread_start(void *arg) {
    printf("hello, this is thrad [%u]\n", (unsigned int)pthread_self());
    pthread_cleanup_push(handlers, "one");
    pthread_cleanup_push(handlers, "two");
    pthread_cleanup_push(handlers, "three");

    //註意,這裏執行了 pthread_exit() 函數
    pthread_exit("he~he~");

    pthread_cleanup_pop(1);
    pthread_cleanup_pop(2);
    pthread_cleanup_pop(3);

    return NULL;
}

int main() {
    pthread_t pt;
    int errn = pthread_create(&pt, NULL, thread_start, NULL);

    if(0 != errn) {
        printf("error [%d], when create pthread\n", errn);
        return -1;
    } else {
        printf("create thread success, threadid : [%u]\n", (unsigned int)pt);
    }
    void *r = NULL;
    errn = pthread_join(pt, &r);
    if(0 != errn) {
        printf("error happend when join, errno = [%d]\n", errn);
        return -1;
    } else {
        printf("thread [%u] over\n", (unsigned int)pt);
    }
    if(NULL != r) {
        printf("thread return : [%s]\n", (const char*)r);
    }

    return 0;
}

編譯並運行:

#include <stdio.h>
#include <pthread.h>

void handlers(void *arg) {
    if(NULL != arg) {
        printf("%s() : [%s]\n", __func__, (char*)arg);
    } else {
        printf("%s()\n", __func__);
    }
}

void *
thread_start(void *arg) {
    printf("hello, this is thrad [%u]\n", (unsigned int)pthread_self());
    pthread_cleanup_push(handlers, "one");
    pthread_cleanup_push(handlers, "two");
    pthread_cleanup_push(handlers, "three");

    //註意,這裏執行了 pthread_exit() 函數
    pthread_exit("he~he~");

    pthread_cleanup_pop(1);
    pthread_cleanup_pop(2);
    pthread_cleanup_pop(3);

    return NULL;
}

int main() {
    pthread_t pt;
    int errn = pthread_create(&pt, NULL, thread_start, NULL);

    if(0 != errn) {
        printf("error [%d], when create pthread\n", errn);
        return -1;
    } else {
        printf("create thread success, threadid : [%u]\n", (unsigned int)pt);
    }
    void *r = NULL;
    errn = pthread_join(pt, &r);
    if(0 != errn) {
        printf("error happend when join, errno = [%d]\n", errn);
        return -1;
    } else {
        printf("thread [%u] over\n", (unsigned int)pt);
    }
    if(NULL != r) {
        printf("thread return : [%s]\n", (const char*)r);
    }

    return 0;
}

使用 return 退出線程

先來看一個使用 return 退出線程的例子:

#include <stdio.h>
#include <pthread.h>

void *
thread_start(void *arg) {
    printf("hello, this is thread [%u]\n", (unsigned int)pthread_self());

    return "ok";
}

int main() {
    pthread_t pt;
    int errn = pthread_create(&pt, NULL, thread_start, NULL);

    if(0 != errn) {
        printf("error [%d], when create pthread\n", errn);
        return -1;
    } else {
        printf("create thread success, threadid : [%u]\n", (unsigned int)pt);
    }
    void *r = NULL;
    errn = pthread_join(pt, &r);
    if(0 != errn) {
        printf("error happend when join, errno = [%d]\n", errn);
        return -1;
    } else {
        printf("thread [%u] over\n", (unsigned int)pt);
    }
    if(NULL != r) {
        printf("thread return : [%s]\n", (const char*)r);
    }

    return 0;
}

編譯並運行:

gcc -g -c -o pthread-return.o pthread-return.c -Wall -I./
gcc -g -o pthread-return pthread-return.o -Wall -I./ -lpthread

./pthread-return 
create thread success, threadid : [722429696]   //主線程打印的信息
hello, this is thread [722429696]               //新創建的線程打印的信息
thread [722429696] over                         //主線程打印的信息
thread return : [ok]                            //主線程打印的信息,其中[ok]為新創建的線程打印的信息

既然 return 和 pthread_exit() 函數都是結束線程,並返回數據,那麽它們之間的區別是什麽呢?

區別就在於,使用 return 退出線程的時候,不會執行線程使用 pthread_cleanup_push(3) 註冊的清理函數。 可以再寫一個例子,看看效果。

#include <stdio.h>
#include <pthread.h>

void handlers(void *arg) {
    if(NULL != arg) {
        printf("%s() : [%s]\n", __func__, (char*)arg);
    } else {
        printf("%s()\n", __func__);
    }
}

void *
thread_start(void *arg) {
    printf("hello, this is thrad [%u]\n", (unsigned int)pthread_self());
    pthread_cleanup_push(handlers, "one");
    pthread_cleanup_push(handlers, "two");
    pthread_cleanup_push(handlers, "three");

    //註意,這裏執行了 return
    return "he~he~";

    pthread_cleanup_pop(1);
    pthread_cleanup_pop(2);
    pthread_cleanup_pop(3);

    return "ok";
}

int main() {
    pthread_t pt;
    int errn = pthread_create(&pt, NULL, thread_start, NULL);

    if(0 != errn) {
        printf("error [%d], when create pthread\n", errn);
        return -1;
    } else {
        printf("create thread success, threadid : [%u]\n", (unsigned int)pt);
    }
    void *r = NULL;
    errn = pthread_join(pt, &r);
    if(0 != errn) {
        printf("error happend when join, errno = [%d]\n", errn);
        return -1;
    } else {
        printf("thread [%u] over\n", (unsigned int)pt);
    }
    if(NULL != r) {
        printf("thread return : [%s]\n", (const char*)r);
    }

    return 0;
}

編譯並運行:

gcc -g -c -o pthread-return.o pthread-return.c -Wall -I./
gcc -g -o pthread-return pthread-return.o -Wall -I./ -lpthread

./pthread-return 
create thread success, threadid : [4185097984]
hello, this is thrad [4185097984]
thread [4185097984] over
thread return : [he~he~]

可以看出,確實沒有執行清理函數,為什麽呢?

因為pthread_cleanup_push(3) 和 pthread_cleanup_pop() 是使用宏實現的。 在 pthread_cleanup_push() 和 pthread_cleanup_pop() 之間,是一個大個的 do{}while(0), 遇到 return 當然就直接退出啦。 具體的實現方式請看這裏, 因為本文只講述一下線程的創建和退出, 所以 pthread_cleanup_push 和 pthread_cleanup_pop 的說明放在其它地方了。

使用 pthread_cancel() 退出線程

先看一下函數原型:

#include <pthread.h>

int pthread_cancel(pthread_t thread);

//Compile and link with -pthread.

其中的 thread 參數就是目的線程的線程ID

pthread_cancel() 函數會給 thread 指定的線程發送一個取消請求。 至於目標線程是否以及合適對這個請求進行反應,則視目標線程的兩個屬性而定: 取消屬性的 state 和 type

一個線程的取消屬性的 state 由 pthread_setcancelstate(3) 函數來設置, 可以是 enabled (一個新創建的線程的默認方式就是 enabled)或者 disabled。 如果一個線程的取消屬性設置了 disabled ,那麽對著個線程發送的取消請求會一直存在, 直到線程恢復了取消屬性的設置。如果一個線程的取消屬性設置了 enabled , 那麽取消屬性的 type 就由取消消息什麽什麽時候到來而決定了。

一個線程的取消類型(type)由 pthread_setcanceltype(3) 函數來設置。 可以是異步的,也可以是延緩的。異步取消屬性的意味著線程任何時間都可以被取消 (通常是立即被取消,但操作系統不保證這一點)。延緩取消是說,取消操作會被延遲, 直到線程接下來的調用的函數是個取消點。在 pthreads(7) (Linux 命令行中執行 man 7 pthreads) 中列出的函數就是或者是取消點。

當一個取消請求起作用時,下面的步驟會按順序發生。

    1. 取消清理函數會被出棧並被執行。
    1. 線程相關數據會被析構,順序不確定。
    1. 線程終止。

以上的步驟會異步的執行,pthread_cancel() 函數的返回狀態會指出取消請求是否成功的發給了制定的線程。

在一個被取消的線程終止後,使用 pthread_join(3) 函數 join 時,會得到線程的結束狀態為 PTHREAD_CANCELED 。 join 一個線程是知道這個取消操作是否完成的唯一方法。

下面是 man pthread_cancel 手冊中的一段示例代碼:

#include <pthread.h>
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>

#define handle_error_en(en, msg)     do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

    static void *
thread_func(void *ignored_argument)
{
    int s;

    /*  Disable cancellation for a while, so that we don‘t
     *                immediately react to a cancellation request */

    s = pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
    if (s != 0)
        handle_error_en(s, "pthread_setcancelstate");

    printf("thread_func(): started; cancellation disabled\n");
    sleep(5);
    printf("thread_func(): about to enable cancellation\n");

    s = pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    if (s != 0)
        handle_error_en(s, "pthread_setcancelstate");

    /*  sleep() is a cancellation point */

    sleep(1000);        /*  Should get canceled while we sleep */

    /*  Should never get here */

    printf("thread_func(): not canceled!\n");
    return NULL;
}

    int
main(void)
{
    pthread_t thr;
    void *res;
    int s;

    /*  Start a thread and then send it a cancellation request */

    s = pthread_create(&thr, NULL, &thread_func, NULL);
    if (s != 0)
        handle_error_en(s, "pthread_create");

    sleep(2);           /*  Give thread a chance to get started */

    printf("main(): sending cancellation request\n");
    s = pthread_cancel(thr);
    if (s != 0)
        handle_error_en(s, "pthread_cancel");

    /*  Join with thread to see what its exit status was */

    s = pthread_join(thr, &res);
    if (s != 0)
        handle_error_en(s, "pthread_join");

    if (res == PTHREAD_CANCELED)
        printf("main(): thread was canceled\n");
    else
        printf("main(): thread wasn‘t canceled (shouldn‘t happen!)\n");
    exit(EXIT_SUCCESS);
}

編譯並運行 :

./pthread_cancel 
thread_func(): started; cancellation disabled
main(): sending cancellation request
thread_func(): about to enable cancellation
main(): thread was canceled

同步地址:https://www.fengbohello.top/archives/linux-pthread-lifecycle

POSIX 線程的創建與退出