linux環境下的線程有joinable
和unjoinable
兩種狀態,joinable
下的線程在完成線程函數(調用pthread_exit()
或者return
函數后)之后不會自動釋放資源,需要在主線程中調用pthread_join()
函數進行阻塞回收,而unjoinable
狀態的線程在完成子線程函數之后可以自動釋放資源(不需要主線程擦屁股)。創建線程時可以通過指定參數來定義線程的狀態(默認為joinable
),也可以通過pthread_detach(pthid)
函數來將子線程的狀態轉換為``unjoinable,由于在主線程中調用
pthread_join()會使得主線程阻塞(等待當前子線程的返回),所以一般情況下會把線程的狀態調為
unjoinable`,從最大限度的增大主線程的性能。
在linux下創建和管理線程的方法聲明在pthread.h
文件中,下面對幾個基本函數進行解析:
pthread_create
該函數的作用是創建一個進程,其函數原型為:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg)
參數解析:
pthread_t *thread
:線程id的地址,線程id由pthread_t
來定義,如pthread_t pthid
。const pthread_attr_t *attr
:該參數指定線程的狀態,比如可以指定detach
狀態,如果為NULL
則為選擇默認的joinable
。void *(*start_routine) (void *)
:函數指針,指向子線程的回調函數,需要注意的是該函數返回的是一個空指針,傳入的也是一個空指針。void *arg
:向回調函數傳入的參數,注意類型是空指針(傳入地址),如果你傳入的參數是一個非空指針,需要在回調函數中將空指針強制轉換為你想要的類型,因為在傳入參數的時候,其他類型的指針會退化為空指針。比如你傳入了一個整型變量a
的指針,需要在回調函數中有以下代碼int* a = (int*)arg
,才可以在回調函數中使用整型變量a
.這里的參數盡量傳入一個自定義指針,原因在最后一節詳解。
pthread_detach
該函數的功能是將子線程detach(分離),將線程的狀態改為unjoinable
,其函數聲明為int pthread_detach(pthread_t thread)
,傳入的參數是線程id。
pthread_join
該函數一般在主線程中調用,用來將主線程阻塞,以等待當前的子線程執行完畢(主線程速度比子線程快,如果不等待可能主線程結束了,子線程還沒執行完就因此關閉了),其函數原型為:
int pthread_join(pthread_t thread, void **retval)
參數解析:
pthread_t thread
:等待的線程idvoid **retval
:存儲子線程的返回值,(子線程返回值一般由pthread_exit()
返回),如果填入NULL
表示不保存返回值。這里需要注意的還是返回值的類型是空指針的指針,如果需要使用該返回值,也需要做強制類型轉換。
注:我們一般在編寫多線程的程序比如多線程的服務器時,一般不會使用pthread_join
這個函數,原因有下:1,調用該函數之后,主線程會在該函數的調用處進行阻塞,等待子線程執行完畢才能繼續向下執行,這樣就會大大拖慢主線程的效率,2,由于主線程使用循環創建多線程,如果使用了該函數,主線程只有再一個線程執行完成之后才會再去創建一個線程,也就是每一個線程是等前一個線程執行完之后在創建再去執行,這樣的多線程顯然效率不高。 一般情況之下,我們會調用pthread_detach
函數,使得子線程分離,狀態變為unjoinable
,這樣每一個線程執行完之后自動釋放內存,主線程也就不需要調用pthread_join
函數進行阻塞,而是可以去循環創建多個子線程,提高程序效率。
pthread_create擴展
我們在寫一個多線程程序時比如多線程的服務器,會使用循環創建多線程,而且我們知道主線程的運行速度一般快于子線程,所以在創建線程時就存在一個問題,那就是我們應該如何傳參,如果傳入一個變量的地址,有可能子線程在沒有利用該變量執行完程序時變量的值就被主線程修改了,所以我們應該傳入一個自定義指針,這樣相當于為每一個子線程的參數單獨創建了一塊地址,不會被主線程修改,之前的情況相當于多個子線程的參數共享一塊地址,下面看例子:
#include <pthread.h>
#include <iostream>
#include <unistd.h>
#include <string.h>
using namespace std;
void* print(void* arg){
sleep(1);
int* i = (int *)arg;
std::cout <<i<<" "<< *i<< std::endl; //打印變量地址和值
}
int main(int argc, const char** argv) {
int i = 0;
while (i<30)
{
int a = i; //是使變量a等于i
pthread_t pthid;
pthread_create(&pthid,NULL,print,&a);//創建線程
pthread_detach(pthid); //改變線程狀態
++i;
}
sleep(3);
return 0;
}
上述程序中主線程循環創建子線程并將循環累加的變量i
的值賦給子線程進行打印結果過為:
我們發現,所有打印的所有變量的值均為30(16進制,我也不知咋回事),這是因為在子線程中我們先睡眠了1秒鐘,這期間主線程的循環早已經完成,將i
的值累加到了30,看上面的輸出結果中所有的地址都是一樣的,都是主線程中定義的a
的地址。
如果我們每一次循環都定義一個指針a
,情況會怎么樣呢,先看代碼:
void* print(void* arg){
sleep(1);
int* i = (int *)arg;
std::cout <<i<<" "<< *i<< std::endl; //打印變量地址和值
}
int main(int argc, const char** argv) {
int i = 0;
while (i<30)
{
int* a = new int(i); //自定義指針
pthread_t pthid;
pthread_create(&pthid,NULL,print,a);//創建線程
pthread_detach(pthid); //改變線程狀態
++i;
}
sleep(3);
return 0;
}
注:一定在堆上分配空間(即使用new分配空間,返回一個指針),千萬不要使用int *a = i
這種在棧上分配空間的方法,因為int *a = i
指明a
就是定義一個指向某一變量的指針,在每次循環中它指向的地址不會發生改變(與前面的實驗結果一致),使用int* a = new int(i)
每一次循環都會開辟一個整型空間并返回一個指針,所以每次循環中a
指向的空間不一樣的,看實驗結果:
這個圖沒有截完整,其實1-30每一個數字都有的,不過影響不大,因為我們可以很清晰的看出每一個參數的地址都不一樣,故而輸出的值都各不相同(1-30)。