「前言」文章是關(guān)于Linux多線程方面的知識(shí),上一篇是 Linux多線程詳解(一),今天這篇是 Linux多線程詳解(二),講解會(huì)比較細(xì),下面開始!
「歸屬專欄」Linux系統(tǒng)編程
「主頁(yè)鏈接」個(gè)人主頁(yè)
「筆者」楓葉先生(fy)
「楓葉先生有點(diǎn)文青病」「每篇一句」
縱有千古,橫有八荒;
前途似海,來日方長(zhǎng)。
——梁?jiǎn)⒊?/span>
目錄
三、 Linux線程控制
3.1?POSIX線程庫(kù)
3.2?線程創(chuàng)建
3.3?線程終止
3.4?線程等待
3.5 線程分離
3.6 重新認(rèn)識(shí)pthread庫(kù)
3.7 封裝線程
三、 Linux線程控制
3.1?POSIX線程庫(kù)
前面我們使用的 pthread線程庫(kù)是歸屬 POSIX線程庫(kù),pthread線程庫(kù)是?POSIX線程庫(kù)的一部分,POSIX線程庫(kù)也叫原生線程庫(kù),遵守 POSIX標(biāo)準(zhǔn):
- 與線程有關(guān)的函數(shù)構(gòu)成了一個(gè)完整的系列,絕大多數(shù)函數(shù)的名字都是以“pthread_”打頭的
- 要使用這些函數(shù)庫(kù),要通過引入頭文<pthread.h>
- 鏈接這些線程函數(shù)庫(kù)時(shí)要使用編譯器命令的 “-lpthread” 選項(xiàng)
POSIX線程庫(kù)錯(cuò)誤檢查:
- 傳統(tǒng)的一些函數(shù)是,成功返回0,失敗返回-1,并且對(duì)全局變量errno賦值以指示錯(cuò)誤。
- pthreads函數(shù)出錯(cuò)時(shí)不會(huì)設(shè)置全局變量errno(而大部分其他POSIX函數(shù)會(huì)這樣做),而是將錯(cuò)誤代碼通過返回值返回
- pthreads同樣也提供了線程內(nèi)的 errno 變量,以支持其它使用 errno 的代碼。對(duì)于 pthreads函數(shù)的錯(cuò)誤,建議通過返回值業(yè)判定,因?yàn)樽x取返回值要比讀取線程內(nèi)的errno變量的開銷更小
3.2?線程創(chuàng)建
這個(gè)在上一篇多線程(一)已經(jīng)詳細(xì)介紹了一部分,這里就不贅述了
線程創(chuàng)建使用的函數(shù)是 pthread_create
函數(shù):pthread_create
作用: pthread_create - create a new thread(創(chuàng)建一個(gè)新線程)
頭文件:#include <pthread.h>
函數(shù)原型
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
參數(shù)
第一個(gè)參數(shù)thread,代表線程ID,是一個(gè)輸出型參數(shù),pthread_t是一個(gè)無(wú)符號(hào)整數(shù)
第二次參數(shù)attr,用于設(shè)置創(chuàng)建線程的屬性,傳入空表示使用默認(rèn)屬性
第三個(gè)參數(shù)start_routine,是一個(gè)函數(shù)的地址,該參數(shù)表示新線程啟動(dòng)后要跳轉(zhuǎn)執(zhí)行的代碼
第四個(gè)參數(shù)arg,是start_routine函數(shù)的參數(shù),用于傳入
返回值
成功返回0,失敗返回錯(cuò)誤碼
下面進(jìn)行代碼測(cè)試,讓主線程創(chuàng)建一批線程,注意循環(huán)創(chuàng)建線程時(shí)沒有進(jìn)行sleep
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
void* start_routine(void* args)
{
string name = static_cast<const char*>(args);//static_cast 安全的進(jìn)行強(qiáng)制類型轉(zhuǎn)換,C++11
while(1)
{
cout << "new thread create success, 線程編號(hào):" << name << endl;
sleep(1);
}
}
int main()
{
#define NUM 10
//創(chuàng)建一批線程
for(int i = 0; i < NUM; i++)
{
pthread_t tid;
char namebuffer[64];
snprintf(namebuffer, sizeof(namebuffer), "%s:%d", "thread", i+1);//i+1 使線程下標(biāo)從1開始
// pthread_create(&tid, nullptr, start_routine, (void*)"thread one");
pthread_create(&tid, nullptr, start_routine, namebuffer);//給線程帶上編號(hào)
//沒有進(jìn)行 sleep
//sellp(1);
}
//主線程
while(1)
{
cout << "new thread create success, I am main thread" << endl;
sleep(1);
}
return 0;
}
編譯運(yùn)行,觀察現(xiàn)象,現(xiàn)象一:線程的編號(hào)都是一樣的,并不是我們預(yù)想的從 1、2、3...開始
保持進(jìn)程運(yùn)行,ps -aL 查看,10個(gè)線程確實(shí)創(chuàng)建出來了,加上主線程一共 11個(gè)線程
把 sleep 注釋的代碼放開,再次編譯運(yùn)行
運(yùn)行結(jié)果,現(xiàn)象二:編號(hào) 1-10 都有了
現(xiàn)象一解釋:
- 主線程創(chuàng)建新線程太快了,新線程都沒有機(jī)會(huì)運(yùn)行,主線程就把10個(gè)新線程創(chuàng)建完畢了,
- 而傳參namebuffer傳過去的是 緩沖區(qū)namebuffer的起始地址,
- 第十個(gè)線程創(chuàng)建完成之后,緩沖區(qū)的內(nèi)容都被第十個(gè)線程的編號(hào)內(nèi)容覆蓋了,所以第一次現(xiàn)象線程的編號(hào)都是 10
注意:創(chuàng)建的新線程誰(shuí)先運(yùn)行??答案是不確定,完全由調(diào)度器決定
上面的 start_routine函數(shù),被多個(gè)執(zhí)行流執(zhí)行,該函數(shù)處于可重入狀態(tài)。start_routine函數(shù)也是可重入函數(shù),因?yàn)闆]有產(chǎn)生二義性
測(cè)試代碼,對(duì)每個(gè)線程的的cnt進(jìn)行取地址,觀察地址是否相同
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
//把參數(shù)封成結(jié)構(gòu)體
class ThreadData
{
public:
pthread_t tid;
char namebuffer[64];
};
void* start_routine(void* args)
{
ThreadData* td = static_cast<ThreadData*>(args);//static_cast 安全的進(jìn)行強(qiáng)制類型轉(zhuǎn)換,C++11
int cnt = 10;
while(cnt)
{
cout << "cnt:" << cnt-- << " &cnt:" << &cnt << endl;
sleep(1);
}
delete td;
return nullptr;
}
int main()
{
#define NUM 10
//創(chuàng)建一批線程
for(int i = 0; i < NUM; i++)
{
ThreadData* td = new ThreadData();//每次循環(huán)new的都是一個(gè)新對(duì)象
snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);//i+1 使線程下標(biāo)從1開始
pthread_create(&td->tid, nullptr, start_routine, td);
}
//主線程
while(1)
{
cout << "new thread create success, name:main thread" << endl;
sleep(1);
}
return 0;
}
編譯運(yùn)行,觀察到每個(gè)線程的cnt地址都不一樣
在函數(shù)內(nèi)部定義的變量叫局部變量,具有臨時(shí)性,在多線程的情況下依舊適用,因?yàn)槊總€(gè)線程都有自己的獨(dú)立棧結(jié)構(gòu)
獲取線程ID?
常見獲取線程ID的方式有兩種:
- 創(chuàng)建線程時(shí)通過輸出型參數(shù)獲得
- 通過調(diào)用pthread_self函數(shù)獲得
?man 3 pthread_self 查看:
函數(shù):pthread_self
頭文件:#include <pthread.h>
函數(shù)原型: pthread_t pthread_self(void);
?測(cè)試代碼,創(chuàng)建一個(gè)新線程,新線程獲取自己的線程ID
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void* start_routine(void* args)
{
string name = static_cast<const char*>(args);
while(1)
{
cout << name << " running..., ID:" << pthread_self() << endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, start_routine, (void*)"thread 1:");
while(1)
{
cout << "main thread" << endl;
sleep(1);
}
return 0;
}
編譯運(yùn)行
3.3?線程終止
如果需要只終止某個(gè)線程而不終止整個(gè)進(jìn)程,可以有三種方法:
- 從線程函數(shù)return。這種方法對(duì)主線程不適用,從main函數(shù)return相當(dāng)于調(diào)用exit,整個(gè)進(jìn)程退出
- 線程可以調(diào)用 pthread_ exit 終止自己
- 一個(gè)線程可以調(diào)用 pthread_ cancel 終止同一進(jìn)程中的另一個(gè)線程
return終止線程
在線程中使用return代表當(dāng)前線程退出,但是在main函數(shù)中使用return代表整個(gè)進(jìn)程退出,也就是說只要主線程退出了那么整個(gè)進(jìn)程就退出了,此時(shí)該進(jìn)程曾經(jīng)申請(qǐng)的資源就會(huì)被釋放,而其他線程會(huì)因?yàn)闆]有了資源,自然而然的也退出了
測(cè)試代碼,主線程創(chuàng)建3個(gè)新線程后,休眠2秒,然后進(jìn)行return,那么整個(gè)進(jìn)程也就退出了?
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
class ThreadData
{
public:
pthread_t tid;
char namebuffer[64];
};
void* start_routine(void* args)
{
ThreadData* td = static_cast<ThreadData*>(args);//static_cast 安全的進(jìn)行強(qiáng)制類型轉(zhuǎn)換,C++11
int cnt = 10;
while(cnt)
{
cout << "new thread create success, name:" << td->namebuffer << " cnt:" << cnt-- << endl;
sleep(1);
}
delete td;
return nullptr;
}
int main()
{
#define NUM 3
//創(chuàng)建一批線程
for(int i = 0; i < NUM; i++)
{
ThreadData* td = new ThreadData();
snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);//i+1 使線程下標(biāo)從1開始
pthread_create(&td->tid, nullptr, start_routine, td);
}
//主線程
cout << "new thread create success, name:main thread" << endl;
sleep(2);//主線程兩秒后退出
return 0;
}
?編譯運(yùn)行,2秒后整個(gè)進(jìn)程退出
如果其他線程執(zhí)行到return,代表該線程結(jié)束,線程退出
pthread_exit函數(shù)終止線程
注意:exit 是用來終止進(jìn)程的,任何一個(gè)執(zhí)行流調(diào)用 exit,都會(huì)使整個(gè)進(jìn)程退出
pthread_exit函數(shù)的功能就是終止線程,man 3 pthread_exit 查看:
- 函數(shù):pthread_exit
- 頭文件:#include <pthread.h>
- 函數(shù)原型: void pthread_exit(void *retval);
- 參數(shù)retval,線程退出時(shí)的退出碼信息,如果不關(guān)心,可以設(shè)置為nullptr
- 無(wú)返回值,跟進(jìn)程一樣,線程結(jié)束的時(shí)候無(wú)法返回它的調(diào)用者(自身)
例如,在下面代碼中,創(chuàng)建了3個(gè)線程,我們使用pthread_exit函數(shù)終止線程
#include <iostream>
#include <unistd.h>
#include <pthread.h>
using namespace std;
class ThreadData
{
public:
pthread_t tid;
char namebuffer[64];
};
void* start_routine(void* args)
{
ThreadData* td = static_cast<ThreadData*>(args);//static_cast 安全的進(jìn)行強(qiáng)制類型轉(zhuǎn)換,C++11
int cnt = 10;
while(cnt)
{
cout << "new thread create success, name:" << td->namebuffer << " cnt:" << cnt-- << endl;
sleep(1);
pthread_exit(nullptr);//每個(gè)線程執(zhí)行到這里就會(huì)退出
}
delete td;
return nullptr;
}
int main()
{
#define NUM 3
//創(chuàng)建一批線程
for(int i = 0; i < NUM; i++)
{
ThreadData* td = new ThreadData();
snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);
pthread_create(&td->tid, nullptr, start_routine, td);
}
//主線程
while(1)
{
cout << "new thread create success, name: main thread" << endl;
sleep(1);
}
return 0;
}
編譯運(yùn)行
需要注意:pthread_exit或者return返回的指針?biāo)赶虻膬?nèi)存單元必須是全局的或者是用malloc分配的,不能在線程函數(shù)的棧上分配,因?yàn)楫?dāng)其它線程得到這個(gè)返回指針時(shí)線程函數(shù)已經(jīng)退出了?
pthread_cancel函數(shù)取消線程
線程是可以被取消的,我們可以使用pthread_cancel函數(shù)取消某一個(gè)線程
man 3 pthread_cancel 查看:
函數(shù):pthread_cancel
頭文件:#include <pthread.h>
函數(shù)原型:
int pthread_cancel(pthread_t thread);
參數(shù):
thread:被取消線程的ID
返回值:線程取消成功返回0,失敗返回錯(cuò)誤碼
?測(cè)試代碼,讓線程執(zhí)行5秒后再取消線程
#include <iostream>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
using namespace std;
class ThreadData
{
public:
int number; //線程編號(hào)
pthread_t tid;//線程ID
char namebuffer[64];//緩沖區(qū)
};
void* start_routine(void* args)
{
ThreadData* td = static_cast<ThreadData*>(args);
int cnt = 10;
while(cnt)
{
cout << td->namebuffer << " cnt:" << cnt-- << endl;
sleep(1);
}
pthread_exit((void*)td->number);
}
int main()
{
vector<ThreadData*> threads;
#define NUM 5
//創(chuàng)建一批線程
for(int i = 0; i < NUM; i++)
{
ThreadData* td = new ThreadData();
td->number = i+1;//線程編號(hào)
snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", td->number);//把需要傳遞的參數(shù)信息格式化到緩沖區(qū)namebuffer中
pthread_create(&td->tid, nullptr, start_routine, td);
threads.push_back(td);//把每個(gè)線程的信息push到threads里面
}
//主線程
for(auto& iter : threads)
{
cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " success" << endl;
}
sleep(5);
//線程取消
for(auto& iter : threads)
{
pthread_cancel(iter->tid);
cout << "pthread_cancel: " << iter->namebuffer << endl;
}
//線程等待
for(auto& iter : threads)
{
void* ret = nullptr;
int n = pthread_join(iter->tid, &ret);
assert(n == 0);
cout << "join: " << iter->namebuffer << " success, thread_exit_code: " << (long long)ret << endl;
delete iter;
}
cout << "main thread quit" << endl;
return 0;
}
編譯運(yùn)行,
注意:一個(gè)線程被取消,它的退出碼是 -1
3.4?線程等待
一個(gè)線程被創(chuàng)建出來,這個(gè)線程就如同進(jìn)程一般,也是需要被等待的。如果主線程不對(duì)新線程進(jìn)行等待,那么這個(gè)新線程的資源也是不會(huì)被回收的。所以線程需要被等待,如果不等待會(huì)產(chǎn)生類似于“僵尸進(jìn)程”的問題,也就是內(nèi)存泄漏,關(guān)于線程產(chǎn)生類似于“僵尸進(jìn)程”的問題,我們無(wú)法查看,線程并沒有類似于僵尸進(jìn)程的概念
- 已經(jīng)退出的線程,其空間沒有被釋放,仍然在進(jìn)程的地址空間內(nèi)。
- 創(chuàng)建新的線程不會(huì)復(fù)用剛才退出線程的地址空間
進(jìn)行線程等待的函數(shù)是 pthread_join,功能:進(jìn)行線程等待
man 3 pthread_join 查看:
函數(shù):pthread_join
頭文件:?#include <pthread.h>
函數(shù)原型:int pthread_join(pthread_t thread, void **retval);?
參數(shù):
thread:被等待線程的ID
retval:線程退出時(shí)的退出碼信息,不關(guān)心設(shè)置為nullptr
返回值:
線程等待成功返回0,失敗返回錯(cuò)誤碼
例如,在下面的測(cè)試代碼中我們先不關(guān)心線程的退出信息,進(jìn)行線程等待
#include <iostream>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
using namespace std;
class ThreadData
{
public:
pthread_t tid;
char namebuffer[64];
};
void* start_routine(void* args)
{
ThreadData* td = static_cast<ThreadData*>(args);
int cnt = 5;
while(cnt)
{
cout << td->namebuffer << " cnt:" << cnt-- << endl;
sleep(1);
}
return nullptr;
}
int main()
{
vector<ThreadData*> threads;
#define NUM 5
//創(chuàng)建一批線程
for(int i = 0; i < NUM; i++)
{
ThreadData* td = new ThreadData();
snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i+1);//i+1 使線程下標(biāo)從1開始
pthread_create(&td->tid, nullptr, start_routine, td);
threads.push_back(td);//把每個(gè)線程的信息push到threads里面
}
//主線程
for(auto& iter : threads)
{
cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " success" << endl;
}
//線程等待
for(auto& iter : threads)
{
int n = pthread_join(iter->tid, nullptr);
assert(n == 0);
cout << "join: " << iter->namebuffer << "success" << endl;
delete iter;
}
cout << "main thread quit" << endl;
return 0;
}
編譯運(yùn)行
下面談線程退出碼的問題,即返回值的問題
- void* retval 和 void** retval 有什么關(guān)系??
- 線程函數(shù)start_routine函數(shù)的返回值類型也是 void*,?start_routine函數(shù)的返回值返回到哪里??
- 我們?cè)趺传@取線程的退出碼,即線程的返回值??
- pthread_join函數(shù)的參數(shù) void** retval 是一個(gè)輸出型參數(shù),用來獲取線程函數(shù)結(jié)束時(shí),返回的退出結(jié)果
- void** retval 是用來獲取線程函數(shù)返回的退出結(jié)果,因?yàn)榫€程函數(shù)的返回值是 void*,所以需要用 void** 來接受 void*
- 注意:線程函數(shù)返回的退出結(jié)果是返回在線程庫(kù)當(dāng)中,參數(shù) void** retval 需要去線程庫(kù)里面接受才可以返回
tips:指針是一個(gè)地址(字面值),是一個(gè)右值,指針變量是一個(gè)變量(變量里面保存著指針的地址),是一個(gè)左值,現(xiàn)在使用的Linux一般是64位的,所以指針是占8字節(jié)的
測(cè)試代碼,線程函數(shù)返回 66666,pthread_join函數(shù)的第二個(gè)參數(shù)去線程庫(kù)里面接受再返回
#include <iostream>
#include <vector>
#include <cassert>
#include <unistd.h>
#include <pthread.h>
using namespace std;
class ThreadData
{
public:
int number; //線程編號(hào)
pthread_t tid;//線程ID
char namebuffer[64];//緩沖區(qū)
};
void* start_routine(void* args)
{
ThreadData* td = static_cast<ThreadData*>(args);
int cnt = 5;
while(cnt)
{
cout << td->namebuffer << " cnt:" << cnt-- << endl;
sleep(1);
}
//return (void*)td->number;//返回線程的編號(hào),返回在線程庫(kù)中,函數(shù)的返回類型是void*,需要進(jìn)行強(qiáng)轉(zhuǎn)void*
return (void*)66666;//方便觀察
}
int main()
{
vector<ThreadData*> threads;
#define NUM 5
//創(chuàng)建一批線程
for(int i = 0; i < NUM; i++)
{
ThreadData* td = new ThreadData();
td->number = i+1;//線程編號(hào)
snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", td->number);//把需要傳遞的參數(shù)信息格式化到緩沖區(qū)namebuffer中
pthread_create(&td->tid, nullptr, start_routine, td);
threads.push_back(td);//把每個(gè)線程的信息push到threads里面
}
//主線程
for(auto& iter : threads)
{
cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " success" << endl;
}
//線程等待
for(auto& iter : threads)
{
void* ret = nullptr;//用于接收線程函數(shù)的返回值
int n = pthread_join(iter->tid, &ret);//對(duì)ret取地址 == void**,需要去線程庫(kù)中接收,再返回
assert(n == 0);
//原來ret是void*,需要強(qiáng)轉(zhuǎn)int,恢復(fù)整型;
//由于我所處的平臺(tái)是64位的,指針是8字節(jié),不能用int進(jìn)行強(qiáng)轉(zhuǎn),會(huì)報(bào)錯(cuò),因?yàn)閕nt是4字節(jié),
//需要用 long long 進(jìn)行強(qiáng)轉(zhuǎn),long long 是8字節(jié)
cout << "join: " << iter->namebuffer << " success, threadnumber: " << (long long)ret << endl;
delete iter;
}
cout << "main thread quit" << endl;
return 0;
}
編譯運(yùn)行,pthread_join函數(shù)成功獲取線程函數(shù)的返回值
解釋如下圖:
pthread_exit函數(shù):void pthread_exit(void *retval),這個(gè)也是返回線程的退出信息,測(cè)試結(jié)果如下:
注意:調(diào)用pthread_join函數(shù)的線程將掛起等待,直到線程終止等待成功。
int pthread_join(pthread_t thread, void **retval);
thread線程以不同的方法終止,通過pthread_join得到的終止?fàn)顟B(tài)是不同的,總結(jié)如下:
- 如果thread線程通過return返回,retval 所指向的單元里存放的是thread線程函數(shù)的返回值。
- 如果thread線程被別的線程調(diào)用 pthread_ cancel 異常終掉,retval 所指向的單元里存放的是常數(shù) PTHREAD_ CANCELED(-1)。
- 如果thread線程是自己調(diào)用 pthread_exit 終止的,retval 所指向的單元存放的是傳給 pthread_exit 的參數(shù)。
- 如果對(duì)thread線程的終止?fàn)顟B(tài)不感興趣,可以傳空給 retval 參數(shù)
3.5 線程分離
- 默認(rèn)情況下,新創(chuàng)建的線程是joinable(可以被等待)的,線程退出后,需要對(duì)其進(jìn)行 pthread_join 操作,否則無(wú)法釋放資源,從而造成系統(tǒng)泄漏
- 如果不關(guān)心線程的返回值,join是一種負(fù)擔(dān),這個(gè)時(shí)候我們可以將該線程進(jìn)行分離,當(dāng)線程退出時(shí),自動(dòng)釋放線程資源
- 分離線程的函數(shù)叫做pthread_detach
man 3 pthread_detach 查看:
函數(shù):pthread_detach
detach:分開,脫離
頭文件:#include <pthread.h>
函數(shù)原型:
int pthread_detach(pthread_t thread);
參數(shù):
thread:被分離線程的ID
返回值:
線程分離成功返回0,失敗返回錯(cuò)誤碼
?測(cè)試代碼,創(chuàng)建了一個(gè)新線程,然后對(duì)新線程進(jìn)行分離,那么此后主線程就不需要在對(duì)新線程進(jìn)行join了
#include <iostream>
#include <pthread.h>
#include <unistd.h>
using namespace std;
string changeID(const pthread_t& thread_id)
{
char tid[128];
snprintf(tid, sizeof(tid), "0x%x", thread_id);
return tid;
}
void* start_routine(void* args)
{
string threadname = static_cast<const char*>(args);
int cnt = 5;
while(cnt--)
{
cout << threadname << " running..., threadID:" << changeID(pthread_self()) << endl;
sleep(1);
}
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, start_routine, (void*)"thread 1");
pthread_detach(tid);//分離線程
//線程默認(rèn)是 joinable的,線程分離了之后不允許進(jìn)行等待
//pthread_join(tid, nullptr);
string mainID = changeID(pthread_self());//主線程ID
while(1)
{
cout << "main running..., mainID:" << mainID << ", new threadID:" << changeID(tid) << endl;
sleep(1);
}
return 0;
}
編譯運(yùn)行
3.6 重新認(rèn)識(shí)pthread庫(kù)
從語(yǔ)言上理解pthread庫(kù)
C++11也有自己的線程庫(kù),測(cè)試代碼如下,使用C++11的線程庫(kù)創(chuàng)建一個(gè)新線程
#include <iostream>
#include <unistd.h>
#include <thread>
void thread_run()
{
while (true)
{
std::cout << "我是新線程..." << std::endl;
sleep(1);
}
}
int main()
{
//創(chuàng)建新線程
std::thread t1(thread_run);
//主線程
while (true)
{
std::cout << "我是主線程..." << std::endl;
sleep(1);
}
//線程等待
t1.join();
return 0;
}
?編譯運(yùn)行,在Linux上也能創(chuàng)建線程?
如何看待C++11中的線程庫(kù)??
任何語(yǔ)言,在linux中如果要實(shí)現(xiàn)多線程,必定要是用pthread庫(kù)。C++11的多線程,在Linux環(huán)境中,本質(zhì)是對(duì)pthread庫(kù)的封裝?
從內(nèi)核上理解pthread庫(kù)
- 用戶創(chuàng)建的線程在 pthread庫(kù)中,pthread庫(kù)又會(huì)幫我們調(diào)用系統(tǒng)調(diào)用接口clone,幫我們創(chuàng)建輕量級(jí)線程
- Linux用戶級(jí)線程 : 內(nèi)核輕量級(jí)線程 = 1 : 1
- 這兩個(gè)的關(guān)系是一對(duì)一的,用戶創(chuàng)建一個(gè)線程,在內(nèi)核中就會(huì)創(chuàng)建一個(gè)輕量級(jí)進(jìn)程
- 用戶關(guān)心的線程屬性在 pthread中,而內(nèi)核提供的是線程執(zhí)行流的調(diào)度
如何理解我們前面創(chuàng)建線程時(shí)的線程ID??如何理解每個(gè)線程的獨(dú)立棧結(jié)構(gòu)???
- pthread_t類型的線程ID,本質(zhì)就是一個(gè)進(jìn)程地址空間上的一個(gè)地址
進(jìn)程運(yùn)行時(shí)動(dòng)態(tài)庫(kù)被加載到內(nèi)存,然后通過頁(yè)表映射到進(jìn)程地址空間中的共享區(qū),此時(shí)該進(jìn)程內(nèi)的所有線程都是能看到這個(gè)動(dòng)態(tài)庫(kù)的
每一個(gè)新線程在共享區(qū)都有這樣一塊區(qū)域?qū)π戮€程的描述,因此我們要找到一個(gè)用戶級(jí)線程只需要找到該線程內(nèi)存塊的起始地址,然后就可以獲取到該線程的各種信息
- 所以,pthread_t類型的線程ID,本質(zhì)就是一個(gè)進(jìn)程地址空間上的一個(gè)地址,指向這個(gè)線程結(jié)構(gòu)體的開始地址
- 每個(gè)線程都有一個(gè)獨(dú)立的棧結(jié)構(gòu),這個(gè)棧就在描述線程的結(jié)構(gòu)體里面,即在pthread庫(kù)中
- 每個(gè)線程創(chuàng)建之后,都是使用自己獨(dú)立的棧
- 主線程所用的棧,也就是我平時(shí)所說的棧,地址空間中的棧
3.7 封裝線程
目的:對(duì)Linux線程接口進(jìn)行封裝,使封裝后的接口可以像C++11線程庫(kù)里面的一樣使用
Thread.hpp
#pragma once
#include <iostream>
#include <string>
#include <cstring>
#include <cassert>
#include <functional>
#include <pthread.h>
// 異常 == if: 意料之外用異?;蛘遡f判斷
// assert: 意料之中用assert
//Context需要使用Thread,需要聲明一下
class Thread;
//上下文,當(dāng)成一個(gè)大號(hào)的結(jié)構(gòu)體
class Context
{
public:
Thread *this_;
void *args_;
public:
Context():this_(nullptr), args_(nullptr)
{}
~Context(){}
};
class Thread
{
public:
typedef std::function<void*(void*)> func_t;//線程函數(shù)
const int num = 1024;
public:
//構(gòu)造初始化并創(chuàng)建線程
Thread(func_t func, void *args = nullptr, int number = 0): func_(func), args_(args)
{
char buffer[num];
snprintf(buffer, sizeof buffer, "thread-%d", number);
name_ = buffer;
Context *ctx = new Context();
ctx->this_ = this;
ctx->args_ = args_;
int n = pthread_create(&tid_, nullptr, start_routine, ctx);
//編譯debug的方式發(fā)布的時(shí)候存在,release方式發(fā)布,assert就不存在了,n就是一個(gè)定義了
assert(n == 0);
//但是沒有被使用的變量,在有些編譯器下會(huì)有warning
(void)n;
}
void *run(void *args){ return func_(args); }
// 類內(nèi)成員,有缺省參數(shù)this指針,無(wú)法直接調(diào)用對(duì)應(yīng)的函數(shù),func_(args_)error
// 在類內(nèi)創(chuàng)建線程,想讓線程執(zhí)行對(duì)應(yīng)的方法,需要將方法設(shè)置成為static
// 靜態(tài)方法不能調(diào)用成員方法或者成員變量
// 所以需要借助一個(gè)結(jié)構(gòu)體Context,完成調(diào)用函數(shù)的工作
static void *start_routine(void *args)
{
Context *ctx = static_cast<Context *>(args);
void *ret = ctx->this_->run(ctx->args_);
delete ctx;
return ret;
}
// 線程等待函數(shù)
void join()
{
int n = pthread_join(tid_, nullptr);
assert(n == 0);
(void)n;
}
~Thread() {}
private:
std::string name_;//線程名稱
func_t func_;//線程要執(zhí)行的函數(shù)
void* args_;//給線程函數(shù)傳遞的參數(shù)
pthread_t tid_;//線程ID
};
測(cè)試代碼
#include <iostream>
#include <unistd.h>
#include "Thread.hpp"
using namespace std;
//新線程
void* start_routine(void* args)
{
string name = static_cast<const char*>(args);
while(1)
{
cout << "new thread create success, name: " << name << endl;
sleep(1);
}
}
int main()
{
//不想傳(void*)"thread 1", 1 參數(shù),還需要進(jìn)行封裝
Thread t1(start_routine, (void*)"thread 1", 1);
while(1)
{
cout << "main thread" << endl;
sleep(1);
}
return 0;
}
運(yùn)行結(jié)果
線程創(chuàng)建完結(jié),下一篇進(jìn)入互斥量和生產(chǎn)消費(fèi)者模型文章來源:http://www.zghlxwxcb.cn/news/detail-430233.html
--------------------- END ----------------------文章來源地址http://www.zghlxwxcb.cn/news/detail-430233.html
「 作者 」 楓葉先生
「 更新 」 2023.4.30
「 聲明 」 余之才疏學(xué)淺,故所撰文疏漏難免,
或有謬誤或不準(zhǔn)確之處,敬請(qǐng)讀者批評(píng)指正。
到了這里,關(guān)于『Linux』第九講:Linux多線程詳解(二)_ 線程控制的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!