鄭州hi寶貝網(wǎng)站建設(shè)公司定制網(wǎng)站開發(fā)公司
文章目錄
- 一、進(jìn)程控制
- 1.1 POSIX線程庫
- 1.2 創(chuàng)建線程pthread_create
- 1.2.1 創(chuàng)建一批線程
- 1.3 終止線程pthread_exit
- 1.4 線程等待pthread_jion
- 1.4.1 線程的返回值(退出碼)
- 1.5 取消線程pthread_cancel
- 1.6 C++多線程
- 1.7 分離線程pthread_detach
- 二、線程ID值
- 三、線程局部存儲__thread
- 四、原生線程庫的封裝
一、進(jìn)程控制
1.1 POSIX線程庫
這是由原生線程庫提供的,遵守POSIX標(biāo)準(zhǔn)。這個標(biāo)準(zhǔn)就像前面學(xué)過的system V標(biāo)準(zhǔn)。
有以下特點(diǎn):
1?? 與線程有關(guān)的函數(shù)構(gòu)成了一個完整的系列,絕大多數(shù)函數(shù)的名字都是以
pthread_
打頭的。
2?? 要使用這些函數(shù)庫,要通過引入頭文<pthread.h>
。
3?? 鏈接這些線程函數(shù)庫時要使用編譯器命令的-lpthread
選項(xiàng)。
1.2 創(chuàng)建線程pthread_create
#include <pthread.h>int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine) (void *), void *arg);
Compile and link with -pthread.// 鏈接的時候必須加上-lpthreadRETURN VALUE
On success, pthread_create() returns 0;
on error, it returns an error number, and the contents of *thread are undefined.
參數(shù)說明:
thread
:線程id
attr
:線程屬性,直接設(shè)為null
start_routine
:函數(shù)指針
arg
:這個參數(shù)會傳遞進(jìn)start_routine
的void*
參數(shù)中。
這里在鏈接的時候要注意link到系統(tǒng)給的原生線程庫-lpthread
。
對于錯誤的檢查:
像我們以前的函數(shù)基本都是設(shè)置進(jìn)全局變量errno來指示錯誤,而pthreads函數(shù)出錯時不會設(shè)置全局變量errno,因?yàn)槿孔兞繒欢鄠€線程共享。它會將錯誤代碼通過返回值返回。
1.2.1 創(chuàng)建一批線程
#include <iostream>
#include <string>
#include <pthread.h>
#include <vector>
#include <unistd.h>using std::cout;
using std::endl;void* start_stream(void* str)
{std::string name = static_cast<const char*>(str);while(true){cout << "i am new thread: " << name << endl;sleep(1);}
}#define NUM 10int main()
{// 創(chuàng)建一批線程for(int i = 0; i < NUM; i++){pthread_t tid;pthread_create(&tid, nullptr, start_stream, (void*)"new thread");}// 創(chuàng)建完畢while(true){cout << "-----------create success-----------" << endl;sleep(1);}return 0;
}
現(xiàn)在我們給每個線程傳遞進(jìn)去的時候加上一個編號:
for(int i = 1; i <= NUM; i++)
{pthread_t tid;char buf[64];snprintf(buf, sizeof(buf), "%s:%d", "thread", i);pthread_create(&tid, nullptr, start_stream, (void*)buf);
}
為什么會出現(xiàn)這個現(xiàn)象呢?
那么真正的寫法應(yīng)該是什么呢?
我們主要目的是讓每個線程都不會共享buf。
class ThreadData
{
public:pthread_t _tid;char _buf[64];
};void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);while(true){cout << "i am new thread: " << ptd->_buf << endl;sleep(1);}
}#define NUM 10int main()
{// 創(chuàng)建一批線程for(int i = 1; i <= NUM; i++){ThreadData *ptd = new ThreadData();snprintf(ptd->_buf, sizeof(ptd->_buf), "%s:%d", "thread", i);pthread_create(&ptd->_tid, nullptr, start_stream, (void*)ptd);}// 創(chuàng)建完畢while(true){cout << "-----------create success-----------" << endl;sleep(1);}return 0;
}
這樣每個線程所獲得的buf都是不同的。
再加上一個計數(shù)器:
void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);int cnt = 10;while(cnt){// cout << "i am new thread: " << ptd->_buf << " cnt: " << cnt << endl;printf("i am new thread: %s cnt: %d &cnt: 0x%x\n", ptd->_buf, cnt, &cnt);cnt--;sleep(1);}delete ptd;return nullptr;
}
這里可以看到每個線程cnt的地址都不一樣,所以每個線程都有不同的cnt,不會相互影響。
這就證明了每一個線程都有自己獨(dú)立的棧結(jié)構(gòu)。
這個調(diào)用方法函數(shù)就是一個可重入函數(shù),每個線程都會形成獨(dú)立的棧幀。
1.3 終止線程pthread_exit
首先要知道最基本的終止方法:
這里要注意exit
不能用來終止線程,使用exit
整個進(jìn)程都會退出,所以exit是用來終止進(jìn)程的。
POSIX線程庫專門給了一個接口用來結(jié)束終止線程。
#include <pthread.h>void pthread_exit(void *retval);Compile and link with -pthread.
參數(shù)可以默認(rèn)設(shè)置為nullptr
。
void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);int cnt = 10;while(cnt){printf("i am new thread: %s cnt: %d &cnt: 0x%x\n", ptd->_buf, cnt, &cnt);cnt--;sleep(1);}delete ptd;//return nullptr;pthread_exit(nullptr);// 結(jié)束線程
}
可以看到把新建線程結(jié)束后,主線程還在繼續(xù)運(yùn)行。
當(dāng)然也可以使用return來終止。
1.4 線程等待pthread_jion
跟進(jìn)程一樣線程也是要等待的,如果不等待,就會造成內(nèi)存泄漏(類似僵尸進(jìn)程)。
而等待主要是要做:
- 獲取新線程的退出信息。
- 回收新線程對應(yīng)的PCB等內(nèi)核資源,防止內(nèi)存泄漏。
#include <pthread.h>int pthread_join(pthread_t thread, void **retval);Compile and link with -pthread.RETURN VALUE
On success, pthread_join() returns 0;
on error, it returns an error number.
參數(shù)說明:
thread
:要等待的線程。
retval
:后面1.4.1線程的返回值詳細(xì)說明。
class ThreadData
{
public:pthread_t _tid;char _buf[64];
};void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);int cnt = 10;while(cnt){printf("i am new thread: %s cnt: %d &cnt: 0x%x\n", ptd->_buf, cnt, &cnt);cnt--;sleep(1);}delete ptd;// return nullptr;pthread_exit(nullptr);// 結(jié)束線程
}#define NUM 10int main()
{// 創(chuàng)建一批線程std::vector<ThreadData*> tids;for(int i = 1; i <= NUM; i++){ThreadData *ptd = new ThreadData();snprintf(ptd->_buf, sizeof(ptd->_buf), "%s:%d", "thread", i);pthread_create(&ptd->_tid, nullptr, start_stream, (void*)ptd);tids.push_back(ptd);}for(auto& e : tids){printf("creat thread: %s : 0x%x success\n", e->_buf, e->_tid);}return 0;
}
這是沒有等待線程的代碼
可以看到主線程運(yùn)行完就直接結(jié)束了進(jìn)程,所有新線程也全部結(jié)束。
進(jìn)行線程等待的代碼:
class ThreadData
{
public:pthread_t _tid;char _buf[64];
};void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);int cnt = 5;while(cnt){printf("i am new thread: %s cnt: %d &cnt: 0x%x\n", ptd->_buf, cnt, &cnt);cnt--;sleep(1);}// return nullptr;pthread_exit(nullptr);// 結(jié)束線程
}#define NUM 10int main()
{// 創(chuàng)建一批線程std::vector<ThreadData*> tids;for(int i = 1; i <= NUM; i++){ThreadData *ptd = new ThreadData();snprintf(ptd->_buf, sizeof(ptd->_buf), "%s:%d", "thread", i);pthread_create(&ptd->_tid, nullptr, start_stream, (void*)ptd);tids.push_back(ptd);}for(auto& e : tids){printf("creat thread: %s : 0x%x success\n", e->_buf, e->_tid);}// 線程等待for(auto& e : tids){int n = pthread_join(e->_tid, nullptr);assert(n == 0);delete e;}cout << "main thread quit" << endl;return 0;
}
可以看到新線程也都執(zhí)行到了結(jié)尾。
1.4.1 線程的返回值(退出碼)
這里的返回值究竟是干什么的呢?
這樣我們就可以在pthread_jion
的時候來獲取線程結(jié)束的返回值。
class ThreadData
{
public:int _number;pthread_t _tid;char _buf[64];
};void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);int cnt = 5;while(cnt){printf("i am new thread: %s cnt: %d &cnt: 0x%x\n", ptd->_buf, cnt, &cnt);cnt--;sleep(1);}// return nullptr;pthread_exit((void*)ptd->_number);// 結(jié)束線程
}#define NUM 10int main()
{// 創(chuàng)建一批線程std::vector<ThreadData*> tids;for(int i = 1; i <= NUM; i++){ThreadData *ptd = new ThreadData();ptd->_number = i;snprintf(ptd->_buf, sizeof(ptd->_buf), "%s:%d", "thread", i);pthread_create(&ptd->_tid, nullptr, start_stream, (void*)ptd);tids.push_back(ptd);}for(auto& e : tids){printf("creat thread: %s : 0x%x success\n", e->_buf, e->_tid);}// 線程等待for(auto& e : tids){void* ret = nullptr;int n = pthread_join(e->_tid, &ret);assert(n == 0);printf("join success: %d\n", (long long)(ret));delete e;}cout << "main thread quit" << endl;return 0;
}
可以看到拿到了返回值(退出碼)。
那么這個ret是怎么拿到數(shù)據(jù)的呢?
線程結(jié)束時會把返回值寫入pthread庫暫時保存,這時候其實(shí)我們設(shè)置的ret變量類型和庫中臨時保存的類型相同,但是由于這是個函數(shù)調(diào)用,我們沒辦法直接賦值。
所以我們吧ret的地址傳進(jìn)去,函數(shù)內(nèi)部自己幫我們實(shí)現(xiàn)(藍(lán)色)
這里我們思考一個問題:線程退出的時候?yàn)槭裁礇]有跟進(jìn)程退出那樣有退出信號?
因?yàn)樾盘柺前l(fā)給進(jìn)程的,整個進(jìn)程都會被退出,要退出信號也沒有意義了。
所以pthread_jion
默認(rèn)認(rèn)為能夠調(diào)用成功,不考慮異常問題,異常應(yīng)該是進(jìn)程考慮的問題。
1.5 取消線程pthread_cancel
想要取消線程的前提是線程得先跑起來。
#include <pthread.h>int pthread_cancel(pthread_t thread);Compile and link with -pthread.RETURN VALUE
On success, pthread_cancel() returns 0;
on error, it returns a nonzero error number.
我們可以取消一半的線程,觀察這一半和剩余一半的區(qū)別:
class ThreadData
{
public:int _number;pthread_t _tid;char _buf[64];
};void* start_stream(void* args)
{ThreadData *ptd = static_cast<ThreadData *>(args);int cnt = 5;while(cnt){printf("i am new thread: %s cnt: %d &cnt: 0x%x\n", ptd->_buf, cnt, &cnt);cnt--;sleep(1);}// return nullptr;pthread_exit((void*)100);// 結(jié)束線程
}#define NUM 10int main()
{// 創(chuàng)建一批線程std::vector<ThreadData*> tids;for(int i = 1; i <= NUM; i++){ThreadData *ptd = new ThreadData();ptd->_number = i;snprintf(ptd->_buf, sizeof(ptd->_buf), "%s:%d", "thread", i);pthread_create(&ptd->_tid, nullptr, start_stream, (void*)ptd);tids.push_back(ptd);}for(auto& e : tids){printf("creat thread: %s : 0x%x success\n", e->_buf, e->_tid);}// 取消一半的線程for(int i = 0; i < tids.size() / 2; i++){pthread_cancel(tids[i]->_tid);}// 線程等待for(auto& e : tids){void* ret = nullptr;int n = pthread_join(e->_tid, &ret);assert(n == 0);printf("join success: %d\n", (long long)(ret));delete e;}cout << "main thread quit" << endl;return 0;
}
所以線程如果是被取消的,那么它的退出碼是-1。它其實(shí)是一個宏:PTHREAD_CANCELED
。
1.6 C++多線程
我們可以用C++給我們提供的線程庫:
#include <iostream>
#include <thread>
#include <unistd.h>using std::cout;
using std::endl;int main()
{std::thread t([](){while(true){cout << "i am new thread" << endl;sleep(1);};});while(true){cout << "i am main thread" << endl;sleep(1);}t.join();return 0;
}
注意編譯的時候得鏈接-lpthread。
因?yàn)槿魏握Z言想要在linux中實(shí)現(xiàn)多線程,必須要使用pthread庫。而我們現(xiàn)在寫的C++11中的多線程,本質(zhì)上就是對pthread庫的封裝。
在后邊會模擬線程庫的實(shí)現(xiàn)。
1.7 分離線程pthread_detach
默認(rèn)情況下,新創(chuàng)建的線程是joinable的,線程退出后,需要對其進(jìn)行pthread_join操作,否則無法釋放資源,從而造成系統(tǒng)泄漏。
如果不關(guān)心線程的返回值,join是一種負(fù)擔(dān),這個時候,我們可以告訴系統(tǒng),當(dāng)線程退出時,自動釋放線程資源。
先寫一個正常的創(chuàng)建線程:
#include <iostream>
#include <string>
#include <cstdio>
#include <pthread.h>
#include <unistd.h>using std::cout;
using std::endl;std::string GetId(const pthread_t& _id)
{char buf[64];snprintf(buf, sizeof(buf), " 0x%x", _id);return buf;
}void* start_stream(void* args)
{std::string name = static_cast<const char*>(args);while(true){cout << "i am new thread name: " << name << GetId(pthread_self()) << endl;sleep(1);}pthread_exit(nullptr);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, start_stream, (void*)"thread one");cout << "i am main thread, new id: " << GetId(tid) << endl;pthread_join(tid, nullptr);return 0;
}
解釋一下這里的pthread_self()
,哪個線程調(diào)用這個函數(shù),就可以獲得該線程的id。
#include <pthread.h>pthread_t pthread_self(void);Compile and link with -pthread.RETURN VALUE
This function always succeeds, returning the calling thread's ID.
而上面我們進(jìn)過驗(yàn)證看到獲取的確實(shí)是新線程的id。
有了獲取線程id的方法接下來就可以進(jìn)行線程的分離。
#include <pthread.h>int pthread_detach(pthread_t thread);Compile and link with -pthread.RETURN VALUE
On success, pthread_detach() returns 0;
on error, it returns an error number.
因?yàn)槿绻粋€線程設(shè)置了分離,那么這個線程就不能被等待,而如果等待失敗,就會返回錯誤碼:
std::string GetId(const pthread_t& _id)
{char buf[64];snprintf(buf, sizeof(buf), " 0x%x", _id);return buf;
}void* start_stream(void* args)
{std::string name = static_cast<const char*>(args);pthread_detach(pthread_self());// 線程分離int cnt = 5;while(cnt--){cout << "i am new thread name: " << name << GetId(pthread_self()) << endl;sleep(1);}pthread_exit(nullptr);
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, start_stream, (void*)"thread one");cout << "i am main thread, new id: " << GetId(tid) << endl;// 一個線程設(shè)置分離狀態(tài)就不能被等待了int n = pthread_join(tid, nullptr);cout << "error: " << n << "->" << strerror(n) << endl;return 0;
}
但是這里的結(jié)果顯示是正確的。是什么原因呢?
因?yàn)橹骶€程和新線程執(zhí)行順序不確定,新線程還沒來得及分離,主線程就執(zhí)行到j(luò)ion了,主線程就開始阻塞等待了,后續(xù)新線程分離了也不知道。
所以我們讓主線程慢一點(diǎn)執(zhí)行或者讓主線程進(jìn)行分離:
所以一旦有一個線程被分離,那么我們就可以不用管這個線程了,做自己的事情即可。
二、線程ID值
我們可以看到我們打印出來的線程ID:
那么它究竟是什么呢?
我們知道linux創(chuàng)建進(jìn)程要通過pthread庫提供的接口。所以原生庫中可能存在多個線程,那么我們就必須要把這些線程管理起來。而描述這些線程的結(jié)構(gòu)體是由原生線程庫實(shí)現(xiàn)的,每一個結(jié)構(gòu)體對應(yīng)一個輕量級進(jìn)程,所以線程是由庫和OS共同實(shí)現(xiàn)的。
而庫中描述線程的結(jié)構(gòu)體到底是什么樣子的呢?
每個結(jié)構(gòu)體在庫中就像一個數(shù)組一樣被存儲起來。而每個結(jié)構(gòu)體的起始位置的地址就是線程的ID值。
這里也可以看到每個線程都有自己獨(dú)立的棧結(jié)構(gòu),由庫來維護(hù)。
主線程也有自己獨(dú)立的棧:
三、線程局部存儲__thread
int g_val = 0;void* start_stream(void* args)
{std::string name = static_cast<const char *>(args);while(true){cout << name;printf(" g_val: %d &g_val 0x%x\n", g_val, &g_val);sleep(1);++g_val;}
}int main()
{pthread_t tid;pthread_create(&tid, nullptr, start_stream, (void*)"thread one");while(true){printf("main g_val: %d &g_val 0x%x\n", g_val, &g_val);sleep(1);}pthread_join(tid, nullptr);return 0;
}
可以看到不同的線程共享一個全局變量。
但是當(dāng)我們給g_val添加__thread
屬性后:
從結(jié)果看g_val不是共享的了,而是每個線程獨(dú)有一個。
添加__thread
,可以將一個內(nèi)置類型設(shè)置為線程局部存儲。給每個線程都來一份。
是介于全局變量和局部變量之間線程特有的屬性。
四、原生線程庫的封裝
我們想像1.6C++多線程那樣來使用多線程該怎么辦呢?
當(dāng)然是對原生線程庫進(jìn)行封裝。
// mythread.hpp
#pragma once#include <iostream>
#include <pthread.h>
#include <cstring>
#include <string>
#include <cassert>
#include <functional>class Thread;class Context
{
public:Thread *_this;void *_args;Context(): _this(nullptr), _args(nullptr){}
};class Thread
{
public:typedef std::function<void*(void*)> func_t;Thread(func_t fun, void* args, int number): _func(fun), _args(args){char buf[64];snprintf(buf, sizeof buf, "thread-%d", number);_name = buf;}// 不加static就會有this指針static void* start_routine(void* args){//return _func(args);// 無this指針,無法調(diào)用Context* pct = static_cast<Context*>(args);pct->_this->_func(pct->_args);delete pct;return nullptr;}void start(){// int n = pthread_create(&_tid, nullptr, _func, _args);// _func是C++函數(shù),pthread_create是C接口,不能混編Context* pct = new Context();pct->_this = this;pct->_args = _args;int n = pthread_create(&_tid, nullptr, start_routine, pct);assert(n == 0);(void)n;}void join(){int n = pthread_join(_tid, nullptr);assert(n == 0);(void)n;}
private:std::string _name;// 線程名pthread_t _tid;// 線程idfunc_t _func;// 調(diào)用方法void *_args;// 參數(shù)
};// mythread.cc
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <pthread.h>
#include <unistd.h>
#include "mythread.hpp"using std::cout;
using std::endl;void* start_handler(void* args)
{std::string name = static_cast<const char *>(args);while(true){cout << name << endl;sleep(1);}
}int main()
{Thread* thread1 = new Thread(start_handler, (void*)"thread one", 1);Thread* thread2 = new Thread(start_handler, (void*)"thread two", 2);Thread* thread3 = new Thread(start_handler, (void*)"thread three", 3);thread1->start();thread2->start();thread3->start();thread1->join();thread2->join();thread3->join();return 0;
}