多線程學(xué)習(xí)指南
目錄
- 什么是多線程?
- 為什么使用多線程?
- 如何創(chuàng)建線程?
- joinable()?
- 多線程參數(shù)傳遞方式
- 鎖
- 原子變量
- 條件變量
- async
- 多線程周邊
- 關(guān)于多線程的一些建議
什么是多線程?
不介紹,基礎(chǔ)知識(shí),直接看維基百科:https://zh.wikipedia.org/wiki/多線程
為什么要用多線程?不介紹,基礎(chǔ)知識(shí),和上面在一個(gè)鏈接。
C 多線程知識(shí)點(diǎn)
如何創(chuàng)建線程?多線程有多種創(chuàng)建方式:pthread、std::thread、std::jthread。
這里我推薦學(xué)習(xí)C 11引入的std::thread,它較pthread更方便,且在C 中更加常用。
至于std::jthread,不用管,學(xué)完std::thread后自然就能學(xué)會(huì)std::jthread。
關(guān)于C 11的多線程具體介紹可以看c 11新特性之線程相關(guān)所有知識(shí)點(diǎn)
使用std::thread創(chuàng)建線程很簡(jiǎn)單,直接利用它的構(gòu)造函數(shù)即可:
void func() { xxxx;}
int main() { std::thread t(func); if (t.joinable()) { t.join(); } return 0;}注意上面代碼,我使用了一個(gè)joinable()和join(),為什么要這么做?因?yàn)槿绻贿@么調(diào)用,在thread生命周期結(jié)束時(shí),程序會(huì)crash。原因直接看thread的析構(gòu)函數(shù):
~thread(){ if (joinable()) std::terminate();}join()和detach()?上面介紹了不調(diào)用join,程序會(huì)crash,其實(shí)也可以調(diào)用detach來避免程序crash,那它倆有什么區(qū)別?
join()表示阻塞等待子線程執(zhí)行結(jié)束,子線程結(jié)束后才會(huì)繼續(xù)往下執(zhí)行。
detach()表示與當(dāng)前對(duì)象分離,子線程無論做啥,無論是否執(zhí)行結(jié)束都與我無關(guān),愛咋咋地,最終靠操作系統(tǒng)回收相關(guān)資源。
joinable()是什么?上面代碼中出現(xiàn)了joinable(),可以簡(jiǎn)單理解為如果沒有調(diào)用join()或者detach(),joinable()就返回true。如果調(diào)用了其中一個(gè),joinable()就返回false。它主要就是為了搭配join()和detach()使用。
參數(shù)傳遞問題多線程其實(shí)就是開啟一個(gè)線程,運(yùn)行某一個(gè)函數(shù),上面的示例是運(yùn)行的無參函數(shù),那如何運(yùn)行有參函數(shù)?怎么將參數(shù)傳遞進(jìn)去?其實(shí)有好幾種方法傳遞參數(shù),我更傾向于使用的是lambda表達(dá)式,將有參函數(shù) 參數(shù)封裝成無參函數(shù),然后多線程調(diào)用。
示例代碼:
#include #include
void func(int a, int b) { std::cout << "a b = " << a b << std::endl; }
int main() { auto lambda = []() { func(1, 2); }; std::thread t(lambda); if (t.joinable()) { t.join(); } return 0;}關(guān)于lambda表達(dá)式我之前寫過文章介紹,可以看這里:
搞定c 11新特性std::function和lambda表達(dá)式
編譯器如何實(shí)現(xiàn)lambda表達(dá)式?
成員函數(shù)問題很多人可能還有疑問,如果多線程運(yùn)行類對(duì)象的成員函數(shù),這里可以使用和上面相同的方法,lambda表達(dá)式:
#include #include #include
struct A { void Print() { std::cout << "A\n"; }};
int main() { std::shared_ptr a = std::make_shared(); auto func = [a]() { a->Print(); }; std::thread t(func); if (t.joinable()) { t.join(); } return 0;}小知識(shí)點(diǎn)
創(chuàng)建thread對(duì)象的常見方法有下面這兩種:
std::thread a(func);
std::thread *a = new thread(func);delete a;有人在技術(shù)交流群里問過這兩種方式的區(qū)別,相信仔細(xì)閱讀過上面內(nèi)容的你應(yīng)該知道答案!
給個(gè)小提示:兩者對(duì)象一個(gè)在堆上,一個(gè)在棧上,生命周期不同,即thread的析構(gòu)函數(shù)調(diào)用時(shí)機(jī)不同,然后可以再結(jié)合上面介紹的~thread()的實(shí)現(xiàn),思考一下。
為什么需要鎖?因?yàn)槎嗑€程讀寫數(shù)據(jù)可能存在線程安全問題,為了保證線程安全,其中一種方式就是使用鎖。
關(guān)于線程安全問題,隨便去個(gè)網(wǎng)站,比如維基百科、百度百科等,都能找到。https://zh.wikipedia.org/wiki/線程安全
mutex有四種:
- std::mutex:獨(dú)占的互斥量,不能遞歸使用,不帶超時(shí)功能
- std::recursive_mutex:遞歸互斥量,可重入,不帶超時(shí)功能
- std::timed_mutex:帶超時(shí)的互斥量,不能遞歸
- std::recursive_timed_mutex:帶超時(shí)的互斥量,可以遞歸使用
加解鎖方式有三種:
- std::lock_guard:可以RAII方式加鎖
- std::unique_lock:比lock_guard多了個(gè)手動(dòng)加解鎖的功能
- std::scoped_lock:防止多個(gè)鎖順序問題導(dǎo)致的死鎖問題而出世的一把鎖
示例代碼:
std::mutex?mutex; void?func()?{ std::lock_guard lock(mutex); xxxxxxx}原子操作上面介紹過使用鎖可以解決線程安全問題,其實(shí)簡(jiǎn)單的變量,比如整型變量等,可以使用原子操作,C 11的原子操作都在中。
示例代碼:
std::atomic<int> count;
int get() { count.load();}
void set(int c) { count.store(c);}上面這兩個(gè)函數(shù)可以在多線程中任意調(diào)用,不會(huì)出現(xiàn)線程安全問題。
條件變量條件變量是一種同步機(jī)制,可以阻塞一個(gè)線程或多個(gè)線程,直到其他線程對(duì)這些線程通知才會(huì)解除阻塞。這種通知和阻塞就需要用到條件變量。
示例代碼:
class CountDownLatch { public: explicit CountDownLatch(uint32_t count) : count_(count);
void CountDown() { std::unique_lock lock(mutex_); --count_; if (count_ == 0) { cv_.notify_all(); } }
void Await(uint32_t time_ms = 0) { std::unique_lock lock(mutex_); while (count_ > 0) { if (time_ms > 0) { cv_.wait_for(lock, std::chrono::milliseconds(time_ms)); } else { cv_.wait(lock); } } }
uint32_t GetCount() const { std::unique_lock lock(mutex_); return count_; }
private: std::condition_variable cv_; mutable std::mutex mutex_; uint32_t count_ = 0;};有關(guān)條件變量其實(shí)有兩個(gè)坑需要注意,移步這里:使用條件變量的坑你知道嗎
基于任務(wù)的并發(fā)這塊個(gè)人認(rèn)為只需要了解async即可,通過async既可以達(dá)到并發(fā)的目的,也可以拿到并發(fā)執(zhí)行后的結(jié)果。
示例代碼:
#include #include #include #include
using namespace std;
int func(int in) { return in 1; }
int main() { auto res = std::async(func, 5); cout << res.get() << endl; // 阻塞直到函數(shù)返回 return 0;}具體可以看:c 11新特性之線程相關(guān)所有知識(shí)點(diǎn)
也可以看我利用此種方式寫的線程池:C 11線程池
其他
如何使線程休眠?
可以利用std::this_thread和chrono,它倆搭配使得線程休眠很方便,而且休眠時(shí)間也很清晰。可不像C語言的sleep,我每次使用C語言的sleep時(shí)都會(huì)特意去搜索一下,單位究竟是秒還是毫秒。
std::this_thread::sleep_for(std::chrono::milliseconds(10));線程個(gè)數(shù)問題
很多人都會(huì)糾結(jié)線程池開多少個(gè)線程效率最高的問題,假設(shè)CPU個(gè)數(shù)為N,有的資料會(huì)介紹N個(gè)線程效率最高,有的資料會(huì)介紹2N個(gè)線程效率最高。在
static unsigned hardware_concurrency() noexcept;至于需要開多少個(gè)線程,個(gè)人認(rèn)為需要根據(jù)個(gè)性化需求實(shí)際測(cè)試,你測(cè)出來多少個(gè)線程性能最高,就開多少個(gè)線程。
死鎖
死鎖的定義可直接維基百科:https://zh.wikipedia.org/wiki/死鎖
至于如何解決死鎖,可以看:多線程中如何使用gdb精確定位死鎖問題
我關(guān)于多線程還有一些建議,推薦大家看這個(gè):
最后,在我學(xué)習(xí)多線程的過程中,發(fā)現(xiàn)了一篇介紹C 11多線程非常詳細(xì)的博客,也推薦大家看看。
博客鏈接:https://www.cnblogs.com/haippy/p/3284540.html
手?jǐn)]一個(gè)對(duì)象池
這里收集了100多篇C 原創(chuàng)文章(入門進(jìn)階必備)
if-else和switch-case哪個(gè)效率更高?看這四張圖。
從未見過把內(nèi)存玩的如此明白的文章(推薦大家都來看看)
寫出高效代碼的12條建議
推薦幾個(gè)開源庫
分享收藏點(diǎn)贊在看





