OS提供的輕量級進(jìn)程接口
(關(guān)于 用戶 → 庫 → OS :具體可看下面線程地址空間布局)
這個(gè)
clone
我們不用,這是OS提供給第三方庫所用的接口
POSIX線程庫
與線程有關(guān)的函數(shù)構(gòu)成了一個(gè)完整的系列,絕大多數(shù)函數(shù)的名字都是以“
pthread_
”開頭的,要使用這些函數(shù)庫,要通過引入頭文<pthread.h>
,鏈接這些線程函數(shù)庫時(shí)要使用編譯器命令的“-lpthread
”選項(xiàng)
創(chuàng)建線程:
函數(shù)原型:
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
void *(*start_routine) (void *), void *arg);
參數(shù)
-
thread
:返回線程ID -
attr
:設(shè)置線程的屬性,attr為NULL表示使用默認(rèn)屬性 -
start_routine
:是個(gè)函數(shù)地址,線程啟動(dòng)后要執(zhí)行的函數(shù) -
arg
:傳給線程啟動(dòng)函數(shù)的參數(shù)
返回值:成功返回0;失敗返回錯(cuò)誤碼
錯(cuò)誤檢查:
- 傳統(tǒng)的一些函數(shù)是,成功返回0,失敗返回-1,并且對全局變量errno賦值以指示錯(cuò)誤。
-
pthreads
函數(shù)出錯(cuò)時(shí)不會(huì)設(shè)置全局變量errno
(而大部分其他POSIX函數(shù)會(huì)這樣做)。而是將錯(cuò)誤代碼通過返回值返回 -
pthreads
同樣也提供了線程內(nèi)的errno變量,以支持其它使用errno的代碼。對于pthreads函數(shù)的錯(cuò)誤,
建議通過返回值業(yè)判定,因?yàn)樽x取返回值要比讀取線程內(nèi)的errno變量的開銷更小
線程使用
1.如何創(chuàng)建一堆線程
試驗(yàn):創(chuàng)建一批線程:
#include <iostream>
#include <string>
#include <vector>
#include <pthread.h>
#include <unistd.h>
using namespace std;
void *start_routine(void *args)
{
string name = static_cast<const char *>(args);
while (true)
{
cout << "new thread create success , name: " << name << endl;
sleep(1);
}
}
int main()
{
vector<pthread_t> tids;
#define NUM 10
for (int i = 0; i < NUM; i++)
{
pthread_t tid;
char namebuffer[64];
snprintf(namebuffer, sizeof(namebuffer), "%s:%d", "thread", i);
pthread_create(&tid, nullptr, start_routine, namebuffer);
// sleep(1);
}
while (true)
{
cout << "new thread create success , name: main thread" << endl;
sleep(1);
}
return 0;
}
為什么這里連續(xù)輸出9呢?
我們這樣的寫法是有問題的,我們可以對其進(jìn)行更改:
class ThreadData
{
public:
int number;
pthread_t tid;
char namebuffer[64];
};
void *start_routine(void *args)
{
ThreadData *td = static_cast<ThreadData *>(args); // 安全的進(jìn)行強(qiáng)制類型轉(zhuǎn)化
int cnt = 10;
while (cnt)
{
cout << "new thread create success, name: " << td->namebuffer << " cnt: " << cnt-- << endl;
}
delete td;
return nullptr;
}
int main()
{
vector<ThreadData *> threads;
#define NUM 10
for (int i = 0; i < NUM; i++)
{
ThreadData *td = new ThreadData();
td->number = i + 1;
snprintf(td->namebuffer, sizeof(td->namebuffer), "%s:%d", "thread", i + 1);
pthread_create(&td->tid, nullptr, start_routine, td);
threads.push_back(td);
}
for (auto &iter : threads)
{
cout << "create thread: " << iter->namebuffer << " : " << iter->tid << " suceesss" << endl;
}
while (true)
{
cout << "new thread create success , name: main thread" << endl;
sleep(1);
}
return 0;
}
將其創(chuàng)建成結(jié)構(gòu)體,每次創(chuàng)建都new
一個(gè)ThreadData
對象,然后將結(jié)構(gòu)體對象的地址傳遞給start_routine
,每一個(gè)結(jié)構(gòu)體對象都有自己獨(dú)立的緩沖區(qū),這樣就能避免緩沖區(qū)被刷新了的問題。
思考:start_routine
這個(gè)函數(shù)被10個(gè)線程同時(shí)運(yùn)行,它是什么狀態(tài)?函數(shù)里面定義的td
與cnt
在變化會(huì)影響別的線程嗎?start_routine
這個(gè)函數(shù)現(xiàn)在是重入狀態(tài),該函數(shù)是可重入函數(shù),雖然cnt
在變化,但是在函數(shù)內(nèi)定義的變量,都叫做局部變量,具有臨時(shí)性,現(xiàn)在依舊適用在多線程情況下,也沒有問題。其實(shí)每一個(gè)線程都有自己獨(dú)立的棧結(jié)構(gòu)!(打印輸出現(xiàn)在不解釋,這個(gè)輸出是往文件輸出,當(dāng)然只能執(zhí)行一個(gè))
2.線程如何終止
-
return nullptr;
線程函數(shù)結(jié)束,return的時(shí)候,線程就算終止了 -
pthread_exit(nullptr);
我們在start_routine函數(shù)while循環(huán)中終止:
void *start_routine(void *args)
{
sleep(1);
ThreadData *td = static_cast<ThreadData *>(args); // 安全的進(jìn)行強(qiáng)制類型轉(zhuǎn)化
int cnt = 10;
while (cnt)
{
cout << "cnt : " << cnt << " &cnt: " << &cnt << endl;
cnt--;
pthread_exit(nullptr);//終止線程
sleep(1);
}
delete td;
return nullptr;
}
循環(huán)打印線程腳本:
while :; do ps -aL | head -1 && ps -aL | grep mythread;sleep 1;done
我們不能之間在線程中使用exit()
,因?yàn)檫@是直接終止進(jìn)程的,如果有線程調(diào)用這個(gè)函數(shù),那么整個(gè)線程都會(huì)終止,pthread_exit(nullptr);
可以終止新線程而不影響主線程。
3.線程如何取消
線程是可以被取消的但是注意:線程要被取消,前提是這個(gè)線程已經(jīng)跑起來了
函數(shù)原型:
int pthread_cancel(pthread_t thread);
實(shí)操代碼:
//新線程部分代碼
void* start_routine(void *args)
{
//....
return (void*)123;
}
//主線程部分代碼
for(int i = 0; i < threads.size()/2; i++)
{
pthread_cancel(threads[i]->tid);
cout << "pthread_cancel : " << threads[i]->namebuffer << " success" << endl;
}
for (auto &iter : threads)
{
void* tmp=nullptr;
int n = pthread_join(iter->tid, &tmp);
assert(n == 0);
cout << "join thread : " << iter->namebuffer <<"exit : "<< (long long)tmp << endl;
delete iter;
}
線程如果是被取消的,退出碼:-1(PTHREAD_CANCELED)
線程等待
線程也是要被等待的,如果不等待,會(huì)造成類似僵尸進(jìn)程的問題–內(nèi)存泄漏
線程必須也要被等待:
- 獲取新線程的退出信息→可以不關(guān)心退出信息嗎?可以
- 回收新線程對應(yīng)的PCB等內(nèi)核資源,防止內(nèi)存泄漏 – 暫時(shí)無法查看!
線程等待函數(shù)pthread_join
:
函數(shù)原型:
int pthread_join(pthread_t thread, void **retval);
在主線程中等待并釋放:
for (auto &iter : threads)
{
int n = pthread_join(iter->tid,nullptr);
assert(n ==0);
cout << "join thread : " << iter->namebuffer << " success" << endl;
delete iter;
}
線程退出返回值
我們的start_routine
函數(shù)返回值是void*
類型,這個(gè)類型有什么說法嗎?
//新線程部分代碼
void *start_routine(void *args)
{
//....
return (void*)123;
}
//主線程部分代碼:
for (auto &iter : threads)
{
void* tmp=nullptr;
int n = pthread_join(iter->tid, &tmp);
assert(n == 0);
cout << "join thread : " << iter->namebuffer <<"exit : " << (long long)tmp<< " success" << endl;//(long long)類型是因?yàn)槲沂褂玫腖inux版本是64位的
delete iter;
}
//其余代碼跟之前一樣,這里只是增加的或更改了一點(diǎn)
這個(gè)的返回值也跟上述return
是一樣的
pthread_exit((void*)123);
既然假的地址,整數(shù)都能被外部拿到,那么如何返回的是,堆空間的地址呢?對象的地址呢?
返回一個(gè)對象指針:
class ThreadReturn
{
public:
int exit_code;
int exit_result;
};
//新線程部分代碼
void* start_routine(void *args)
{
//.....
ThreadReturn * tr = new ThreadReturn();
tr->exit_code = 1;
tr->exit_result = 123;
return (void*)tr;
}
//主線程部分代碼
for (auto &iter : threads)
{
ThreadReturn* tmp=nullptr;
int n = pthread_join(iter->tid, (void**)&tmp);
assert(n == 0);
cout << "join thread : " << iter->namebuffer <<"exit_code : " << tmp->exit_code<< " exit_result : "<<tmp->exit_result << endl;
delete iter;
}
為什么沒有見到,線程退出的時(shí)候,對應(yīng)的退出信號???
線程出異常,收到信號,整個(gè)進(jìn)程都會(huì)退出!pthread_join
:默認(rèn)就認(rèn)為函數(shù)會(huì)調(diào)用成功!不考慮異常問題,異常問題是進(jìn)程該考慮的問題!
C++11的多線程
#include <iostream>
#include <unistd.h>
#include <thread>
void thread_run()
{
while (true)
{
std::cout << "我是新線程..." << std::endl;
sleep(1);
}
}
int main()
{
std::thread t1(thread_run);
while (true)
{
std::cout << "我是主線程..." << std::endl;
sleep(1);
}
t1.join();
return 0;
}
任何語言,在linux中如果要實(shí)現(xiàn)多線程,必定要是用pthread庫
如何看待C++11中的多線程呢?
C++11 的多線程,在Linux環(huán)境中,本質(zhì)是對pthread庫的封裝!(使用原生線程庫還是C++11的線程庫都可以,只是C++11的線程庫在windows下也能運(yùn)行,Linux與windows底層的線程庫的接口不一樣)
線程ID及地址空間布局
-
pthread_create
函數(shù)會(huì)產(chǎn)生一個(gè)線程ID
,存放在第一個(gè)參數(shù)指向的地址中。該線程ID和前面說的線程ID不是一回事。 - 前面講的線程
ID
屬于進(jìn)程調(diào)度的范疇。因?yàn)榫€程是輕量級進(jìn)程,是操作系統(tǒng)調(diào)度器的最小單位,所以需要一個(gè)數(shù)值來唯一表示該線程。 -
pthread_create
函數(shù)第一個(gè)參數(shù)指向一個(gè)虛擬內(nèi)存單元,該內(nèi)存單元的地址即為新創(chuàng)建線程的線程ID,屬于NPTL
線程庫的范疇。線程庫的后續(xù)操作,就是根據(jù)該線程ID來操作線程的。 - 線程庫
NPTL
提供了pthread_self
函數(shù),可以獲得線程自身的ID
:
#include <iostream>
#include <string>
#include <pthread.h>
#include <unistd.h>
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)
{
std::string threadname = static_cast<const char *>(args);
while (true)
{
std::cout << threadname << " running ... : " << changeId(pthread_self()) << std::endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, start_routine, (void *)"thread 1");
std::string main_id = changeId(pthread_self());
std::cout << "main thread running ... new thread id: " << changeId(tid) <<" main thread id: " << main_id << std::endl;
pthread_join(tid,nullptr);
return 0;
}
線程地址空間布局
pthread_t
到底是什么類型呢?取決于實(shí)現(xiàn)。對于Linux目前實(shí)現(xiàn)的NPTL實(shí)現(xiàn)而言,pthread_t類型的線程ID,本質(zhì)就是一個(gè)進(jìn)程地址空間上的一個(gè)地址。
原生線程庫可能存在多個(gè)線程,我們同樣也要對線程進(jìn)行管理(先描述再組織),每一個(gè)輕量級進(jìn)程對應(yīng)原生庫中的一個(gè)結(jié)構(gòu)體對象(TCB),這是Linux的方案 – 用戶級線程,用戶關(guān)心的線程屬性在庫中,而內(nèi)核里面提供執(zhí)行流的調(diào)度。Linux用戶級線程對比內(nèi)核輕量級進(jìn)程是1:1。
線程布局圖:
用戶級線程:線程ID值就是庫中結(jié)構(gòu)體(TCB)對象的地址
線程局部存儲(chǔ)
我們在之前的“什么是線程?”一文已經(jīng)了解到了:線程一旦被創(chuàng)建,幾乎所有資源都是被線程所共享的
//定義一個(gè)全局變量
int g_val= 100;
//新線程:
std::cout << threadname << " running ... : " << changeId(pthread_self())
<<" g_val: "<< g_val++ << " &g_val: " << &g_val << std::endl;
//主線程:
std::cout << "main thread running ... new thread id: " <<changeId(tid)
<<" main thread id: " << main_id << " g_val: "<< g_val << " &g_val: " << &g_val << std::endl;
我們在主線程與新線程都打印輸出g_val
的值與地址:
我們可以發(fā)現(xiàn),主線程與新線程都是輸出同一個(gè)g_val
值與地址,且這個(gè)地址非常小
添加__thread
,可以將一個(gè)內(nèi)置類型設(shè)置為線程局部存儲(chǔ)
__thread int g_val = 100;
我們可以發(fā)現(xiàn),給g_val
添加__thread
后,主線程與新線程打印輸出的地址不一樣了,新線程對g_val
的值進(jìn)行修改,主線程打印的值沒有變化。
原因圖:
一開始g_val
在已初始化數(shù)據(jù)段,添加__thread
以后,使其設(shè)置為線程局部存儲(chǔ),每個(gè)線程都有一份且是在共享區(qū),已初始化數(shù)據(jù)段→共享區(qū)(地址由低到高),且它們訪問不會(huì)互相影響。
分離線程
- 默認(rèn)情況下,新創(chuàng)建的線程是
joinable
的,線程退出后,需要對其進(jìn)行pthread_join
操作,否則無法釋放資源,從而造成系統(tǒng)泄漏。 - 如果不關(guān)心線程的返回值,
join
是一種負(fù)擔(dān),這個(gè)時(shí)候,我們可以告訴系統(tǒng),當(dāng)線程退出時(shí),自動(dòng)釋放線程資源。 - 如果線程設(shè)置了分離狀態(tài),那么就不能
join
等待了。
分離一個(gè)線程:
函數(shù)原型:
int pthread_detach(pthread_t thread);
可以是線程組內(nèi)其他線程對目標(biāo)線程進(jìn)行分離,也可以是線程自己分離
驗(yàn)證分離以后不能join(里面有小bug,請思考):
#include <iostream>
#include <string>
#include <pthread.h>
#include <cstring>
#include <unistd.h>
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)
{
std::string threadname = static_cast<const char *>(args);
// pthread_detach(pthread_self());
int cnt = 5;
while (cnt--)
{
std::cout << threadname << " running ... : " << changeId(pthread_self()) << std::endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, start_routine, (void *)"thread 1");
std::string main_id = changeId(pthread_self());
std::cout << "main thread running ... new thread id: " << changeId(tid) <<" main thread id: " << main_id << std::endl;
int n = pthread_join(tid,nullptr);
std::cout << "result : " << n << " : " << strerror(n) <<std::endl;
return 0;
}
1.我們在start_routine
里面沒有使用線程分離:
2.我們在start_routine
里面使用線程分離pthread_detach
:
思考:為什么我在start_routine
里面使用線程分離pthread_detach
然后pthread_join
還是成功了?
原因:由于主線程創(chuàng)建新線程以后,到底是主線程先運(yùn)行還是新線程先運(yùn)行是隨機(jī)的,如果主線程先運(yùn)行了pthread_join
使得其已經(jīng)阻塞式等待了,然后新線程才pthread_detach
分離,這個(gè)時(shí)候已經(jīng)晚了。
當(dāng)然這種分離方式我們不贊成,我們一般這樣使用線程分離:
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)
{
std::string threadname = static_cast<const char *>(args);
int cnt = 5;
while (cnt--)
{
std::cout << threadname << " running ... : " << changeId(pthread_self()) << std::endl;
sleep(1);
}
return nullptr;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, start_routine, (void *)"thread 1");
std::string main_id = changeId(pthread_self());
pthread_detach(tid);//線程分離
std::cout << "main thread running ... new thread id: " << changeId(tid) << " main thread id: " << main_id << std::endl;
// int n = pthread_join(tid,nullptr);
// std::cout << "result : " << n << " : " << strerror(n) <<std::endl;
while (true)
{
std::cout << "main thread running ... " << std::endl;
}
return 0;
}
文章來源:http://www.zghlxwxcb.cn/news/detail-606661.html
如有錯(cuò)誤或者不清楚的地方歡迎私信或者評論指出????文章來源地址http://www.zghlxwxcb.cn/news/detail-606661.html
到了這里,關(guān)于【Linux】詳解線程控制 -- 線程用法 | 線程等待 | 線程ID及地址空間布局的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!