多線程學(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來(lái)避免程序crash,那它倆有什么區(qū)別?
join()表示阻塞等待子線程執(zhí)行結(jié)束,子線程結(jié)束后才會(huì)繼續(xù)往下執(zhí)行。
detach()表示與當(dāng)前對(duì)象分離,子線程無(wú)論做啥,無(wú)論是否執(zhí)行結(jié)束都與我無(wú)關(guān),愛(ài)咋咋地,最終靠操作系統(tǒng)回收相關(guān)資源。
joinable()是什么?上面代碼中出現(xiàn)了joinable(),可以簡(jiǎn)單理解為如果沒(méi)有調(diào)用join()或者detach(),joinable()就返回true。如果調(diào)用了其中一個(gè),joinable()就返回false。它主要就是為了搭配join()和detach()使用。
參數(shù)傳遞問(wèn)題多線程其實(shí)就是開(kāi)啟一個(gè)線程,運(yùn)行某一個(gè)函數(shù),上面的示例是運(yùn)行的無(wú)參函數(shù),那如何運(yùn)行有參函數(shù)?怎么將參數(shù)傳遞進(jìn)去?其實(shí)有好幾種方法傳遞參數(shù),我更傾向于使用的是lambda表達(dá)式,將有參函數(shù) 參數(shù)封裝成無(wú)參函數(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á)式我之前寫(xiě)過(guò)文章介紹,可以看這里:
搞定c 11新特性std::function和lambda表達(dá)式
編譯器如何實(shí)現(xiàn)lambda表達(dá)式?
成員函數(shù)問(wèn)題很多人可能還有疑問(wèn),如果多線程運(yùn)行類(lèi)對(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ì)象的常見(jiàn)方法有下面這兩種:
std::thread a(func);
std::thread *a = new thread(func);
delete a;
有人在技術(shù)交流群里問(wèn)過(guò)這兩種方式的區(qū)別,相信仔細(xì)閱讀過(guò)上面內(nèi)容的你應(yīng)該知道答案!
給個(gè)小提示:兩者對(duì)象一個(gè)在堆上,一個(gè)在棧上,生命周期不同,即thread的析構(gòu)函數(shù)調(diào)用時(shí)機(jī)不同,然后可以再結(jié)合上面介紹的~thread()的實(shí)現(xiàn),思考一下。
為什么需要鎖?因?yàn)槎嗑€程讀寫(xiě)數(shù)據(jù)可能存在線程安全問(wèn)題,為了保證線程安全,其中一種方式就是使用鎖。
關(guān)于線程安全問(wè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è)鎖順序問(wèn)題導(dǎo)致的死鎖問(wèn)題而出世的一把鎖
示例代碼:
std::mutex?mutex;
void?func()?{
std::lock_guard lock(mutex);
xxxxxxx
}
原子操作上面介紹過(guò)使用鎖可以解決線程安全問(wèn)題,其實(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)線程安全問(wè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即可,通過(guò)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)
也可以看我利用此種方式寫(xiě)的線程池:C 11線程池
其他
如何使線程休眠?
可以利用std::this_thread和chrono,它倆搭配使得線程休眠很方便,而且休眠時(shí)間也很清晰??刹幌馛語(yǔ)言的sleep,我每次使用C語(yǔ)言的sleep時(shí)都會(huì)特意去搜索一下,單位究竟是秒還是毫秒。
std::this_thread::sleep_for(std::chrono::milliseconds(10));
線程個(gè)數(shù)問(wèn)題
很多人都會(huì)糾結(jié)線程池開(kāi)多少個(gè)線程效率最高的問(wèn)題,假設(shè)CPU個(gè)數(shù)為N,有的資料會(huì)介紹N個(gè)線程效率最高,有的資料會(huì)介紹2N個(gè)線程效率最高。在
static unsigned hardware_concurrency() noexcept;
至于需要開(kāi)多少個(gè)線程,個(gè)人認(rèn)為需要根據(jù)個(gè)性化需求實(shí)際測(cè)試,你測(cè)出來(lái)多少個(gè)線程性能最高,就開(kāi)多少個(gè)線程。
死鎖
死鎖的定義可直接維基百科:https://zh.wikipedia.org/wiki/死鎖
至于如何解決死鎖,可以看:多線程中如何使用gdb精確定位死鎖問(wèn)題
我關(guān)于多線程還有一些建議,推薦大家看這個(gè):
最后,在我學(xué)習(xí)多線程的過(guò)程中,發(fā)現(xiàn)了一篇介紹C 11多線程非常詳細(xì)的博客,也推薦大家看看。
博客鏈接:https://www.cnblogs.com/haippy/p/3284540.html
手?jǐn)]一個(gè)對(duì)象池
這里收集了100多篇C 原創(chuàng)文章(入門(mén)進(jìn)階必備)
if-else和switch-case哪個(gè)效率更高?看這四張圖。
從未見(jiàn)過(guò)把內(nèi)存玩的如此明白的文章(推薦大家都來(lái)看看)
寫(xiě)出高效代碼的12條建議
推薦幾個(gè)開(kāi)源庫(kù)
分享收藏點(diǎn)贊在看