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