歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

在Linux中使用線程

我並不假定你會使用Linux的線程,所以在這裡就簡單的介紹一下。如果你之前有過多線程方面的編程經驗,完全可以忽略本文的內容,因為它非常的初級。

首先說明一下,在Linux編寫多線程程序需要包含頭文件pthread.h。也就是說你在任何采用多線程設計的程序中都會看到類似這樣的代碼:

1 #include <pthread.h>

當然,進包含一個頭文件是不能搞定線程的,還需要連接libpthread.so這個庫,因此在程序連接階段應該有類似這樣的指令:
gcc program.o -o program -lpthread
1. 第一個例子
在Linux下創建的線程的API接口是pthread_create(),它的完整定義是:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *) void *arg);

當你的程序調用了這個接口之後,就會產生一個線程,而這個線程的入口函數就是start_routine()。如果線程創建成功,這個接口會返回0。
start_routine()函數有一個參數,這個參數就是pthread_create的最後一個參數arg。這種設計可以在線程創建之前就幫它准備好一些專有數據,最典型的用法就是使用C++編程時的this指針。start_routine()有一個返回值,這個返回值可以通過pthread_join()接口獲得。
pthread_create()接口的第一個參數是一個返回參數。當一個新的線程調用成功之後,就會通過這個參數將線程的句柄返回給調用者,以便對這個線程進行管理。
pthread_create()接口的第二個參數用於設置線程的屬性。這個參數是可選的,當不需要修改線程的默認屬性時,給它傳遞NULL就行。具體線程有那些屬性,我們後面再做介紹。
好,那麼我們就利用這些接口,來完成在Linux上的第一個多線程程序,見代碼1所示:
#include <stdio.h>
#include <pthread.h>
void* thread( void *arg )
{
    printf( "This is a thread and arg = %d.\n", *(int*)arg);
    *(int*)arg = 0;
    return arg;
}
int main( int argc, char *argv[] )
{
    pthread_t th;
    int ret;
    int arg = 10;
    int *thread_ret = NULL;
    ret = pthread_create( &th, NULL, thread, &arg );
    if( ret != 0 ){
        printf( "Create thread error!\n");
        return -1;
    }
    printf( "This is the main process.\n" );
    pthread_join( th, (void**)&thread_ret );
    printf( "thread_ret = %d.\n", *thread_ret );
    return 0;
}

代碼1第一個多線程編程例子

將這段代碼保存為thread.c文件,可以執行下面的命令來生成可執行文件:
$ gcc thread.c -o thread -lpthread
這段代碼的執行結果可能是這樣:
$ ./thread
This is the main process.
This is a thread and arg = 10.
thread_ret = 0.
注意,我說的是可能有這樣的結果,在不同的環境下可能會有出入。因為這是多線程程序,線程代碼可能先於第24行代碼被執行。
我們回過頭來再分析一下這段代碼。在第18行調用pthread_create()接口創建了一個新的線程,這個線程的入口函數是start_thread(),並且給這個入口函數傳遞了一個參數,且參數值為10。這個新創建的線程要執行的任務非常簡單,只是將顯示“This is a thread and arg = 10”這個字符串,因為arg這個參數值已經定義好了,就是10。之後線程將arg參數的值修改為0,並將它作為線程的返回值返回給系統。與此同時,主進程做的事情就是繼續判斷這個線程是否創建成功了。在我們的例子中基本上沒有創建失敗的可能。主進程會繼續輸出“This is the main process”字符串,然後調用pthread_join()接口與剛才的創建進行合並。這個接口的第一個參數就是新創建線程的句柄了,而第二個參數就會去接受線程的返回值。pthread_join()接口會阻塞主進程的執行,直到合並的線程執行結束。由於線程在結束之後會將0返回給系統,那麼pthread_join()獲得的線程返回值自然也就是0。輸出結果“thread_ret = 0”也證實了這一點。
那麼現在有一個問題,那就是pthread_join()接口干了什麼?什麼是線程合並呢?
2. 線程的合並與分離
我們首先要明確的一個問題就是什麼是線程的合並。從前面的敘述中讀者們已經了解到了,pthread_create()接口負責創建了一個線程。那麼線程也屬於系統的資源,這跟內存沒什麼兩樣,而且線程本身也要占據一定的內存空間。眾所周知的一個問題就是C或C++編程中如果要通過malloc()或new分配了一塊內存,就必須使用free()或delete來回收這塊內存,否則就會產生著名的內存洩漏問題。既然線程和內存沒什麼兩樣,那麼有創建就必須得有回收,否則就會產生另外一個著名的資源洩漏問題,這同樣也是一個嚴重的問題。那麼線程的合並就是回收線程資源了。
線程的合並是一種主動回收線程資源的方案。當一個進程或線程調用了針對其它線程的pthread_join()接口,就是線程合並了。這個接口會阻塞調用進程或線程,直到被合並的線程結束為止。當被合並線程結束,pthread_join()接口就會回收這個線程的資源,並將這個線程的返回值返回給合並者。
與線程合並相對應的另外一種線程資源回收機制是線程分離,調用接口是pthread_detach()。線程分離是將線程資源的回收工作交由系統自動來完成,也就是說當被分離的線程結束之後,系統會自動回收它的資源。因為線程分離是啟動系統的自動回收機制,那麼程序也就無法獲得被分離線程的返回值,這就使得pthread_detach()接口只要擁有一個參數就行了,那就是被分離線程句柄。
線程合並和線程分離都是用於回收線程資源的,可以根據不同的業務場景酌情使用。不管有什麼理由,你都必須選擇其中一種,否則就會引發資源洩漏的問題,這個問題與內存洩漏同樣可怕。
3. 線程的屬性
前面還說到過線程是有屬性的,這個屬性由一個線程屬性對象來描述。線程屬性對象由pthread_attr_init()接口初始化,並由pthread_attr_destory()來銷毀,它們的完整定義是:
int pthread_attr_init(pthread_attr_t *attr);
int pthread_attr_destory(pthread_attr_t *attr);

那麼線程擁有哪些屬性呢?一般地,Linux下的線程有:綁定屬性、分離屬性、調度屬性、堆棧大小屬性和滿占警戒區大小屬性。下面我們就分別來介紹這些屬性。
3.1 綁定屬性
說到這個綁定屬性,就不得不提起另外一個概念:輕進程(Light Weight Process,簡稱LWP)。輕進程和Linux系統的內核線程擁有相同的概念,屬於內核的調度實體。一個輕進程可以控制一個或多個線程。默認情況下,對於一個擁有n個線程的程序,啟動多少輕進程,由哪些輕進程來控制哪些線程由操作系統來控制,這種狀態被稱為非綁定的。那麼綁定的含義就很好理解了,只要指定了某個線程“綁”在某個輕進程上,就可以稱之為綁定的了。被綁定的線程具有較高的相應速度,因為操作系統的調度主體是輕進程,綁定線程可以保證在需要的時候它總有一個輕進程可用。綁定屬性就是干這個用的。
設置綁定屬性的接口是pthread_attr_setscope(),它的完整定義是:
int pthread_attr_setscope(pthread_attr_t *attr, int scope);
它有兩個參數,第一個就是線程屬性對象的指針,第二個就是綁定類型,擁有兩個取值:PTHREAD_SCOPE_SYSTEM(綁定的)和PTHREAD_SCOPE_PROCESS(非綁定的)。代碼2演示了這個屬性的使用。
#include <stdio.h>
#include <pthread.h>
……
int main( int argc, char *argv[] )
{
    pthread_attr_t attr;
    pthread_t th;
    ……
    pthread_attr_init( &attr );
    pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );
    pthread_create( &th, &attr, thread, NULL );
    ……
}

代碼2設置線程綁定屬性
不知道你是否在這裡發現了本文的矛盾之處。就是這個綁定屬性跟我們之前說的NPTL有矛盾之處。在介紹NPTL的時候就說過業界有一種m:n的線程方案,就跟這個綁定屬性有關。但是筆者還說過NPTL因為Linux的“蠢”沒有采取這種方案,而是采用了“1:1”的方案。這也就是說,Linux的線程永遠都是綁定。對,Linux的線程永遠都是綁定的,所以PTHREAD_SCOPE_PROCESS在Linux中不管用,而且會返回ENOTSUP錯誤。
既然Linux並不支持線程的非綁定,為什麼還要提供這個接口呢?答案就是兼容!因為Linux的NTPL是號稱POSIX標准兼容的,而綁定屬性正是POSIX標准所要求的,所以提供了這個接口。如果讀者們只是在Linux下編寫多線程程序,可以完全忽略這個屬性。如果哪天你遇到了支持這種特性的系統,別忘了我曾經跟你說起過這玩意兒:)
3.2 分離屬性
前面說過線程能夠被合並和分離,分離屬性就是讓線程在創建之前就決定它應該是分離的。如果設置了這個屬性,就沒有必要調用pthread_join()或pthread_detach()來回收線程資源了。
設置分離屬性的接口是pthread_attr_setdetachstate(),它的完整定義是:
pthread_attr_setdetachstat(pthread_attr_t *attr, int detachstate);
它的第二個參數有兩個取值:PTHREAD_CREATE_DETACHED(分離的)和PTHREAD_CREATE_JOINABLE(可合並的,也是默認屬性)。代碼3演示了這個屬性的使用。
#include <stdio.h>
#include <pthread.h>
……
int main( int argc, char *argv[] )
{
    pthread_attr_t attr;
    pthread_t th;
    ……
    pthread_attr_init( &attr );
    pthread_attr_setscope( &attr, PTHREAD_SCOPE_SYSTEM );
    pthread_create( &th, &attr, thread, NULL );
    ……
}

代碼3設置線程分離屬性

更多詳情請繼續閱讀第2頁的內容:http://www.linuxidc.com/Linux/2013-10/91381p2.htm

Copyright © Linux教程網 All Rights Reserved