歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Unix知識 >> Unix基礎知識

UNIX環境高級編程:線程

線程包含了表示進程內執行環境必需的信息,其中包括進程中標示線程的線程ID、一組寄存器值、棧、調度優先級和策略、信號屏蔽字、errno變量以及線程私有數據。

進程的所有信息對該進程的所有線程都是共享的,包括可執行的程序文本、程序的全局內存和堆內存、棧以及文件描述符。

線程標識:

進程ID在整個系統中是唯一的,但線程ID不同,線程ID只在它所屬的進程環境中有效。進程ID的數據結構為pid_t,線程ID的數據結構為pthread_t。

比較兩個線程ID是否相等:

#include <pthread.h>  
int pthread_equal(pthread_t tid1,pthread_t tid2);//返回值:若相等返回非0值,否則返回0

線程可以通過調用pthread_self函數獲取自身的線程ID:

#include <pthread.h>  
pthread_t pthread_self(void); //返回值:調用線程的線程ID

線程的創建:

#include <pthread.h>  
int pthread_create(pthread_t *tidp,const pthread_attr_t *attr,void *(*start_rtn)(void *),void * arg);//返回值:若成功則返回0,否則返回錯誤編號

當pthread_create成功返回時,由tidp指向的內存單元被設置為新創建線程的線程ID。attr參數用於定制各種不同的線程屬性。若設置為NULL,表示創建默認屬性的線程。

新創建的線程從start_rtn函數的地址開始運行,該函數只有一個無類型指針參數的arg,如果需要向start_rtn函數傳遞的參數不止一個,那麼需要把這些參數放到一個結構中,然後把這個結構的地址作為arg參數傳入。

線程創建時並不能保證哪個線程會先運行:是新創建的線程還是調用線程。新創建的線程可以訪問進程地址空間,並且繼承調用線程的浮點環境和信號屏蔽字,但是該線程的未決信號集被清除。

每個線程都提供了errno的副本。

注意: pthread_create不是等待start_rtn函數運行完成後才返回的。一般情況下是pthread_create函數先返回,不過有時候start_rtn函數先運行完成後,pthread_create才返回。它們之間沒有先後順序。

示例代碼:

#include <stdio.h>  
#include <stdlib.h>  
#include <pthread.h>  
#include <unistd.h>  
#include <sys/types.h>  
#include <unistd.h>  
      
pthread_t ntid;  
      
void printids( const char *s)  
{  
    pid_t pid;  
    pthread_t tid;  
    pid = getpid();  
    tid = pthread_self();  
    printf("%s pid = %u pthread_t = %u\n",s,(unsigned int)pid, (unsigned int)tid);  
}  
      
void* thr_fn(void * arg)  
{  
    printids("new pthread: ");  
    return NULL;  
}  
      
int main(int argc,char **argv)  
{  
    int err;  
    err = pthread_create(&ntid,NULL,thr_fn,NULL);  
    if ( err != 0 )  
      perror("pthread_create");  
    printids("main thread: ");  
    sleep(1);  
    exit(0);  
}

運行結果:

huangcheng@ubuntu:~$ ./a.out
 new pthread ID = 3077581680  
main thread:  pid = 2458 pthread_t = 3077584576  
new pthread:  pid = 2458 pthread_t = 3077581680

注意在本例裡,主線程把新線程ID存放在ntid中,但是新建的線程並不能安全的使用它,如果新線程在主線程調用pthread_create返回之前就運行了,那麼新線程看到的是未經初始化的ntid的內容,這個內容並不是正確的線程ID。

線程終止:

如果進程中的任一線程調用了exit、_Exit或者_exit,那麼整個進程就會終止。與此類似,如果信號的默認動作是終止進程,那麼把該信號發送到線程會終止整個進程。

單個線程可以通過下列三種方式退出,在不終止整個進程的情況下停止它的控制流:

線程只是從啟動歷程中返回,返回值是線程的退出碼。

線程可以被同一進程中的其他線程取消。

線程調用pthread_exit.

注意:主線程調用pthread_exit也不會導致整個進程結束,其子線程還是繼續運行。

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

rval_ptr是一個無類型的指針。進程中的其他線程可以通過調用pthread_join函數返回到這個指針。

#include <pthread.h>  
int pthread_join(pthread_t thread,void **rval_ptr);//返回值:若成功則返回0 ,否則返回錯誤編號

調用線程將一直阻塞,直到指定的線程調用pthread_exit、從啟動例程中返回或者被取消。

如果線程只是從它的啟動例程返回,rval_ptr將包含返回碼。如果線程被取消,由rval_ptr指定的內存單元就置為PTHREAD_CANCELED。

可以通過調用pthread_join自動把線程置於分離狀態,這樣資源就可以恢復。如果線程已經處於分離狀態,pthread_join調用就會失敗,返回EINVAL。

線程可以通過調用pthread_cancel函數來請求取消同一進程中的其他線程:

#include <pthread.h>  
int pthread_cancel(pthread_t tif); // 返回值:若成功則返回0,否則返回錯誤編號

在默認情況下,pthread_cancel函數會使得由tid標識的線程的行為表現為如同調用了參數為PTHREAD_CANCEL的pthread_exit函數,但是線程可以選擇忽略取消方式或是控制取消方式。注意:pthread_cancel並不等待線程終止,它僅僅提出請求。

清理函數:

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

線程可以安排它退出時需要調用的函數,這與進程可以用atexit函數安排進程退出需要調用的函數時類似的。這樣的函數稱為線程清理函數處理程序。線程可以建立多個清理處理程序。處理程序記錄在棧中的,也就是說它們的執行順序與它們注冊時的順序相反。

pthread_cleanup_push()/pthread_cleanup_pop()采用先入後出的棧結構管理,void routine(void *arg)函數在調用pthread_cleanup_push()時壓入清理函數棧,多次對pthread_cleanup_push() 的調用將在清理函數棧中形成一個函數鏈;從pthread_cleanup_push的調用點到pthread_cleanup_pop之間的程序段中的終止動作(包括調用pthread_exit()和異常終止,不包括return)都將執行pthread_cleanup_push()所指定的清理函數。
pthread_cleanup_push()函數執行壓棧清理函數的操作,而pthread_cleanup_pop()函數執行從棧中刪除清理函數的操作。    

注意pthread_cleanup_pop不管參數是零還是非零,都會從棧頂刪除一個清理函數。

在下面三種情況下,pthread_cleanup_push()壓棧的“清理函數”會被調用,調用參數為arg,清理函數rtn的調用順序是由pthread_cleanup_push函數來安排的。:

線程調用pthread_exit()函數,而不是直接return.

響應取消請求時,也就是有其它的線程對該線程調用pthread_cancel()函數。

本線程調用pthread_cleanup_pop()函數,並且其參數非0.

如果execute參數置為0,清理函數將不被調用。無論哪種情況即不管pthread_cleanup_pop的參數時零還是非零,pthread_cleanup_pop都將刪除上次pthread_cleanup_push調用建立的清理處理程序,即刪除清理函數鏈的棧頂。

pthread_cleanup_push()/pthread_cleanup_pop()是以宏方式實現的,這是pthread.h中的宏定義:

#define pthread_cleanup_push(routine,arg) \
{  
struct _pthread_cleanup_buffer _buffer; \
_pthread_cleanup_push (&_buffer, (routine), (arg));  
#define pthread_cleanup_pop(execute) \
_pthread_cleanup_pop (&_buffer, (execute));  \ 
}

可見pthread_cleanup_push()帶有一個"{",而pthread_cleanup_pop()帶有一個"}",因此這兩個函數必須成對出現,且必須位於程序的同一級別的代碼段中才能通過編譯。

注意:

pthread_exit終止線程與線程直接return終止線程的區別。

pthread_cleanup_push()函數與pthread_cleanup_pop()函數必須成對的出現在同一個函數中。

查看本欄目更多精彩內容:http://www.bianceng.cn/OS/unix/

示例代碼:

#include <stdio.h>  
#include <pthread.h>  
#include <errno.h>  
#include <stdlib.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-1  handler");  
        pthread_cleanup_push(cleanup, "thread 1-2  handler");  
        printf("thread 1 push complete\n");  
      
        if (arg)  
           pthread_exit((void*) 1);  
        pthread_cleanup_pop(0);  
        pthread_cleanup_pop(1);  
      
        return((void*) 10);  
}  
      
void *thr_fn2(void *arg)  
{  
        printf("thread 2 start\n");  
        pthread_cleanup_push(cleanup, "thread 2-1  handler");  
        pthread_cleanup_push(cleanup, "thread 2-2  handler");  
        printf("thread 2 push complete\n");  
      
        pthread_cleanup_pop(0);  
       // if (arg)  
       //         return ((void*) 2);  
      
        pthread_cleanup_pop(1);  
        //pthread_exit((void*) 20);  
        return ((void*) 20);  
}  
      
int main(void)  
{  
        int             err;  
        pthread_t       tid1, tid2;  
        void            *tret;  
      
        err = pthread_create(&tid1, NULL, thr_fn1, (void*)1);  
        if (err)  
                fprintf(stderr, "can't create thread 1: %d\n", strerror(errno));          
      
        err = pthread_create(&tid2, NULL, thr_fn2, (void*)1);  
        if (err)  
                fprintf(stderr, "can't create thread 2: %d\n", strerror(errno));          
      
        err = pthread_join(tid1, &tret);  
        if (err)  
                fprintf(stderr, "can't join with thread 1: %d\n", strerror(errno));       
        printf("thread 1 exit code %lu\n", (unsigned long) tret);  
      
        err = pthread_join(tid2, &tret);  
        if (err)  
                fprintf(stderr, "can't join with thread 2: %d\n", strerror(errno));       
        printf("thread 2 exit code %lu\n", (unsigned long) tret);  
      
      
        exit(0);  
}

運行結果:

huangcheng@ubuntu:~$ ./a.out
thread 2 start  
thread 2 push complete  
cleanup: thread 2-1  handler  
thread 1 start  
thread 1 push complete  
cleanup: thread 1-2  handler  
cleanup: thread 1-1  handler  
thread 1 exit code 1  
thread 2 exit code 20

在默認情況下,線程的終止狀態會保存到對該線程調用pthread_join,如果線程已經處於分離狀態,線程的底層存儲資源可以在線程終止時立即被收回。當線程被分離時,並不能用pthread_join等待它的終止狀態。對分離狀態的線程調用pthread_join會失敗,返回EINVAL。

pthread_detach調用可以用於使線程進入分離狀態:

#include <pthread.h>  
int pthread_detach(pthread_t tid);//返回值:若成功則返回0,否則返回錯誤編號

現在可以看出線程函數和進程函數之間的相似之處:

Copyright © Linux教程網 All Rights Reserved