天天看點

APUE學習之多線程程式設計(一):線程的建立和銷毀

一、線程辨別

     和每個程序都有一個程序ID一樣,每個線程也有一個線程ID,線程ID是以pthread_t資料類型來表示的,在Linux中,用無符号長整型表示pthread_t,Solaris 把phread_t資料類型表示為無符号整型,FreeBSD 和Mac OS X 用一個指向pthread結構的指針來表示pthread_t資料類型。

     可以使用pthread_self函數獲得自身的線程ID。   

#include <pthread.h>
 pthread_t pthread_self(void);      

二、線程建立

     使用pthread_create函數建立新線程 

#include <pthread.h>
int pthread_create(pthread_t *restrict tidp, const pthread_arrt_t *restrict addr, void *(*start_rtn)(void *), void *restrict arg);      

     當pthread_create成功傳回後,新建立線程的線程ID會被設定成tidp指向的記憶體單元,attr參數用于定制各種不同的線程屬性,後面再讨論線程屬性,現在先把它置為null,建立一個具有預設屬性的線程。

     新建立的線程從start_rtn函數開始運作,該函數接收一個無類型指針的參數arg,如果要傳給它的參數多于一個,可以把參數放到一個結構中,然後把結構的位址作為arg傳入。

     線程建立後會繼承調用線程的浮點環境和屏蔽字。

例子:  

APUE學習之多線程程式設計(一):線程的建立和銷毀
APUE學習之多線程程式設計(一):線程的建立和銷毀
#include "apue.h"
#include <pthread.h>

pthread_t ntid;

void printids(const char *s)
{
    pid_t pid;
    pthread_t tid;

    pid = getpid();
    tid = pthread_self();
    printf("%s pid %lu tid %lu (0x%lx)\n", s, (unsigned long)pid, (unsigned long)tid, (unsigned long)tid);
}

void *thr_fn(void *arg)
{
    printids("new thread: ");
    return ((void *)0);
}

int main(void)
{
    int err;

    err = pthread_create(&ntid, NULL, thr_fn, NULL);
    if (err != 0)
    {
        err_exit(err, "can't create thread");
    }

    printids("main thread: ");
    sleep(1);
    exit(0);
}      

View Code

  這個程式有兩個特别的地方:第一,主線程需要休眠,如果主線程不休眠,主線程會退出,新線程并沒有機會運作。第二,新線程通過pthread_self(),獲得自己的線程ID。

APUE學習之多線程程式設計(一):線程的建立和銷毀
APUE學習之多線程程式設計(一):線程的建立和銷毀
./a.out
main thread:  pid 27335 tid 3076404928 (0xb75e36c0)
new thread:  pid 27335 tid 3076401984 (0xb75e2b40)      

  雖然Linux線程ID是用無符号長整型來表示的,但它們看起來更像指針。

三、線程終止

     如果任意線程調用了exit,_exit,_Exit,整個程序都會終止,這個要注意。

     單個線程可以通過以下三種方式退出,且不終止整個程序。

     1.線程可以簡單地從啟動例程中傳回,傳回值是線程的退出碼。

     2.線程可以被同一程序中的其他線程取消。

     3.調用pthread_exit

     先來看pthread_exit退出的情況。

#include <pthread.h>
void pthread_exit(void *rval_ptr);      

     ravl_ptr是無類型指針,程序中的其他線程可以通過pthread_join函數獲得這個指針。

#include <pthread.h>
int pthread_join(pthread_t thread, void **rval_ptr);      

     調用線程将一直阻塞,直至指定的線程退出,rval_ptr就包含傳回碼,如果線程被取消,rval_ptr指定的記憶體單元就設定為PTHREAD_CANCELED.可以通過調用pthread_join自動把線程置于分離狀态,如果線程已處于分離狀态,pthread_join就會調用失敗。

例子:

APUE學習之多線程程式設計(一):線程的建立和銷毀
APUE學習之多線程程式設計(一):線程的建立和銷毀
#include "apue.h"
#include <pthread.h>
 
void *thr_fn1(void *arg)
{
    printf("thread 1 returning\n");
    return (void *)1;
}
 
void *thr_fn2(void *arg)
{
    printf("thread 2 exiting\n");
    pthread_exit((void *)2);
}
 
int main(void)
{
    int err;
    pthread_t tid1, tid2;
    void *tret;
 
    err = pthread_create(&tid1, NULL, thr_fn1, NULL);
 
    if (err != 0)
    {
        err_exit(err, "can't create thread1");
    }
 
    err = pthread_create(&tid2, NULL, thr_fn2, NULL);
 
    if (err != 0)
    {
        err_exit(err, "can't create thread2");
    }
 
    err = pthread_join(tid1, &tret);
 
    if (err != 0)
    {
        err_exit(err, "can't join thread1");
    }
 
    printf("thread1 exit code:%ld\n", (long)tret);
 
    err = pthread_join(tid2, &tret);
 
    if (err != 0)
    {
        err_exit(err, "can't join thread2");
    }
 
    printf("thread2 exit code:%ld\n", (long)tret);
 
    return 0;
}      
APUE學習之多線程程式設計(一):線程的建立和銷毀
APUE學習之多線程程式設計(一):線程的建立和銷毀
./a.out
thread 2 exiting
thread 1 returning
thread1 exit code:1
thread2 exit code:2      

  也可傳遞包含複雜消息的結構的位址,不過必須注意,這個結構所使用的記憶體必須在完成調用後仍是有效的。

      線程也可以調用pthread_cancel函數來請求取消同一程序的其他線程

#include <pthread.h>
int pthread_cancel(pthread_t tid);      

     聽着有點霸道,不過也隻是請求而已,線程可以選擇忽略這個請求。

     線程可以安排它退出時需要調用的函數,這樣的函數是由pthread_cleanup_push注冊在棧中的,是以執行順序與注冊時相反。

#include <pthread.h>
void pthread_cleanup_push(void(*rtn)(void *), void *arg);
void pthread_cleanup_pop(int execute);      

     當線程執行以下動作時,清理函數rtn由pthread_cleanup_push函數排程

     1.調用pthread_exit時

     2.響應取消請求時

     3.用非零execute參數調用pthread_cleanup_pop時。

APUE學習之多線程程式設計(一):線程的建立和銷毀
APUE學習之多線程程式設計(一):線程的建立和銷毀
#include "apue.h"
#include <pthread.h>
 
void cleanup(void *arg)
{
    printf("cleanup: %s\n", (char *)arg);
}
 
void *thr_fn1(void *arg)
{
    printf("thread 1 start\n");
    pthread_cleanup_push(cleanup, "thread 1 first handler");
    pthread_cleanup_push(cleanup, "thread 1 second handler");
    printf("thread 1 push complete\n");
 
    if (arg)
    {
        return (void *)1;
    }
 
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
 
    return (void *)1;
}
 
void *thr_fn2(void *arg)
{
    printf("thread 2 start\n");
    pthread_cleanup_push(cleanup, "thread 2 first handler");
    pthread_cleanup_push(cleanup, "thread 2 second handler");
    printf("thread 2 push complete\n");
 
    if (arg)
    {
        pthread_exit((void *)2);
    }
 
    pthread_cleanup_pop(0);
    pthread_cleanup_pop(0);
 
    return (void *)2;
}
 
int main(void)
{
    int err;
    pthread_t tid1, tid2;
    void *tret;
 
    err = pthread_create(&tid1, NULL, thr_fn1, (void *)1);
    if (err != 0)
    {
        err_exit(err, "can't create thread 1");
    }
 
    err = pthread_create(&tid2, NULL, thr_fn2, (void *)1);
    if (err != 0)
    {
        err_exit(err, "can't create thread 2");
    }
 
    err = pthread_join(tid1, &tret);
    if (err != 0)
    {
        err_exit(err, "can't join with thread 1");
    }
    printf("thread 1 exit code %ld\n", (long)tret);
 
    err = pthread_join(tid2, &tret);
    if (err != 0)
    {
        err_exit(err, "can't join with thread 2");
    }
    printf("thread 2 exit code %ld\n", (long)tret);
 
    return 0;
}      

View Code 

APUE學習之多線程程式設計(一):線程的建立和銷毀
APUE學習之多線程程式設計(一):線程的建立和銷毀
./a.out
thread 2 start
thread 2 push complete
cleanup: thread 2 second handler
cleanup: thread 2 first handler
thread 1 start
thread 1 push complete
thread 1 exit code 1
thread 2 exit code 2      

     可知如果線程是通過它的啟動例程中傳回而終止的話,它的清理處理程式就不會被調用。

     在預設情況下,線程的終止狀态會一直儲存到對該線程調用pthread_join,如果該線程已經被分離,則底層的資源可以線上程終止時立即被收回,不用再調用pthread_join,且再調用pthread_join會出錯。

#include <pthread.h>
int pthread_detach(pthread_t tid);      

繼續閱讀