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