日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 >

【多线程】C++11进行多线程开发 (std::thread)

發布時間:2025/3/21 56 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【多线程】C++11进行多线程开发 (std::thread) 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

  • 創建線程
    • std::thread 類
    • 使用join()
    • 使用 detach()
  • 警惕作用域
  • 線程不能復制
  • 給線程傳參
    • 傳遞指針
    • 傳遞引用
  • 以類成員函數為線程函數
  • 以容器存放線程對象
  • 互斥量
    • std::mutex
    • std::lock_guard
  • 條件變量
  • call_once


創建線程

C++11 增加了線程以及線程相關的類, 而之前并沒有對并發編程提供語言級別的支持

std::thread 類

使用 std::thread 類來創建線程, 我們需要提供的只是線程函數, 或者線程對象, 同時提供必要的參數
std::thread 表示單個執行的線程, 使用thread 類首先會構造一個線程對象, 然后開始執行線程函數,

#include <iostream> #include <thread> //需要包含的頭using namespace std;void func(int a, double b) //有參數, 參數數量不限 {cout << a << ' ' << b << endl; }void func2() //無參數 {cout << "hello!\n"; }int main() {thread t1(func, 1, 2); //提供參數thread t2(func2);//可以使用 lambda表達式thread t3([](int a, double b){cout << a << ' ' << b << endl;}, 3, 4);cout << t1.get_id() << "****" << endl; //可以使用 get_id() 獲取線程 idt1.join();t2.join();t3.join();return 0; }

使用join()

我們知道, 上例中如果主線程 (main) 先退出, 那些還未完成任務的線程將得不到執行機會, 因為 main 會在執行完調用 exit(), 然后整個進程就結束了, 那它的"子線程" (我們知道線程是平級的, 這里只是, 形象一點) 自然也就 over 了
所以就像上例中, 線程對象調用 join() 函數, join() 會阻塞當前線程, 直到線程函數執行結束, 如果線程有返回值, 會被忽略

使用 detach()

對比于 join(), 我們肯定有不想阻塞當前線程的時候, 這時可以調用 detach(), 這個函數會分離線程對象和線程函數, 讓線程作為后臺線程去執行, 當前線程也不會被阻塞了, 但是分離之后, 也不能再和線程發生聯系了, 例如不能再調用 get_id() 來獲取線程 id 了, 或者調用 join() 都是不行的, 同時也無法控制線程何時結束

#include <thread> void func() {//... }int main() {std::thread t(func);t.detach();// 可以做其他事了, 并不會被阻塞return 0; }

程序終止后, 不會等待在后臺執行的其余分離線程, 而是將他們掛起, 并且本地對象被破壞

警惕作用域

std::thread 出了作用域之后就會被析構, 這時如果線程函數還沒有執行完就會發生錯誤, 因此, 要注意保證線程函數的生命周期在線程變量 std::thread 之內

線程不能復制

std::thread 不能復制, 但是可以移動
也就是說, 不能對線程進行復制構造, 復制賦值, 但是可以移動構造, 移動賦值

#include <iostream> #include <thread>void func() {std::cout << "here is func" << std::endl; }int main() {std::thread t1(func);std::thread t2;t2 = t1; //errort2 = std::move(t1); //right, 將 t1 的線程控制權轉移給 t2std::cout << t1.get_id() << std::endl; //error,t1已經失去了線程控制權t1 = std::thread(func); //right, 直接構造, 創建的是臨時對象,所以隱式調用movet1 = std::move(t2); //error, 不能通過賦值一個新值來放棄一個已有線程, 這樣會直接導致程序崩潰 }

std::thread 將 = 重載了, 調用 operator= 是移動構造函數, 復制被禁用了,

給線程傳參

傳遞指針

#include <iostream> #include <thread>void func(int* a){//這里是直接修改指針指向的地址中的值,并不是修改形參指針的指向,所以傳指針可以改變實參的值*a += 10; }int main() {int x = 10;std::thread t1(func, &x);t1.join();std::cout << x << std::endl;return 0; }

上例代碼, 可以如愿改變 x 的值, 但是看下面的代碼, 當我們傳遞引用時, 卻好像并不能如我們所想:

傳遞引用

#include <iostream> #include <thread>void func(int& a) {a += 10; }int main() {int x = 10;std::thread t1(func, x); //編譯會報錯// std::thread t1(func, std::ref(x)); //正確的寫法t1.join();std::cout << x << std::endl;return 0; }

我們想讓 func 函數對 x 進行更新, 但是實際上給線程傳參會以拷貝的形式復制到線程空間, 所以即使是引用, 引用的實際上是新線程堆棧中的臨時值, 為了解決這個問題, 我們需要使用引用包裝器 std::ref()
改成:
std::thread t1(func, std::ref(x));

實際上, 我的編譯器對于std::thread t1(func, x);這段代碼直接給出了編譯錯誤,改成std::thread t1(func, std::ref(x));后就沒問題了…


以類成員函數為線程函數

因為類內成員涉及 this 指針, 就和所需的線程函數參數不同了

#include <iostream> #include <thread>using namespace std;class A { public:void func1() {cout << "here is class A`s func 1" << endl;}static void func2() {cout << "here is class A`s func 2" << endl;}void func3() {thread t1(&A::func1, this); //非靜態成員函數thread t2(A::func2); //靜態成員函數t1.join();t2.join();} };int main() {A a;thread t1(&A::func1, &a); //非靜態成員函數thread t2(A::func2); //靜態成員函數t1.join();t2.join();a.func3(); } 注意的是, 如果我們選擇將成員函數變成靜態的使用, 那我們就不能使用非靜態的成員變量了, 解決辦法也很簡單, 給靜態成員函數傳遞該對象的 this 指針就好了。
  • 非靜態成員函數需要加上& 符號,并且應該加上類名和::作用符,寫在類外就是std::thread t1(&A::func1, &a); ,寫在類中就需要用this,即thread t1(&A::func1, this);;
  • 靜態成員函數不需要加上&,也沒有this。因為靜態成員函數屬于類,不屬于實例對象。即thread t2(A::func2);

可以參考一下我犯過的這個錯誤


以容器存放線程對象

我們可以用容器保存創建的多個線程對象, 而當我們像其中插入元素時, 建議使用 emplace_bcak() 而不是 push_back()。

我們知道 push_back() 會創建一個臨時對象然后拷貝, 當然自從有了移動語意這里出發都是移動, 如下例:

#include <iostream> #include <thread> #include <vector>using namespace std;class A { public:void func1() {cout << "here is class A`s func 1" << endl;}void func3() {tmpThread.push_back(thread(&A::func1, this)); //(1)tmpThread.emplace_back(&A::func1, this); //(2)}vector<thread> tmpThread; };

比較上例中 (1) (2)兩處, 明顯發現emplace_back() 比 push_back() 調用形式更加簡潔, 他會自動推導直接根據你給出的參數初始化臨時對象
emplace_back 不會觸發復制構造和移動構造, 他會直接原地構造一個元素
所以使用 emplace_back 更加簡潔效率也更加高


互斥量

std::mutex

mutex 類是保護共享數據, 避免多線程同時訪問的同步原語;
mutex 也不能復制, 他的operator=被禁用。

  • lock
    上鎖, 若失敗則阻塞
  • try_lock
    嘗試上鎖, 失敗則返回
  • unlock
    解鎖

使用時注意死鎖

std::lock_guard

通常不直接使用 mutex, lock_guard 更加安全, 更加方便。
他簡化了 lock/unlock 的寫法, lock_guard 在構造時自動鎖定互斥量, 而在退出作用域時會析構自動解鎖, 保證了上鎖解鎖的正確操作, 正是典型的 RAII 機制

#include <thread> #include <mutex>std::mutex myLock; void func() {{std::lock_guard<std::mutex> locker(myLock); //出作用域自動解鎖//do some things...}myLock.lock();myLock.unlock(); }int main() {std::thread t(func);t.join(); }

@zhz: 疑問: std::lock_guard<std::mutex> locker(myLock);這句話是鎖定myLock這個互斥量,避免其他線程獲取這個互斥量嗎? 還是說,只鎖住這句話之后的代碼塊,避免別的線程訪問該代碼塊???

  • 答:是鎖住這個互斥量。因為一旦有一個線程的某段代碼鎖住了這個互斥量,其他線程就獲取不了這個鎖的權限了。使用同一個互斥量在不同的地方鎖住,是因為這幾個地方代碼肯定會訪問同一個變量(或者說共享內存區域),不然不需要使用鎖。只有在多線程下才需要使用鎖。哪怕只有一個地方使用鎖,還是必要的,因為不同線程都在同一片代碼塊進行寫操作時,也需要加鎖防止同時在這個地方寫造成寫數據混亂。

還有一些其他互斥量, 如std::recursive::mutex 是遞歸型互斥量, 可以讓同一線程重復申請等等, 就不一一介紹了


條件變量

條件變量是C++11 提供的一種用于等待的同步機制, 可以阻塞一到多個線程, 直到收到另一個線程發出的通知或者超時, 才會喚醒當前阻塞的線程, 條件變量需要和互斥量配合起來使用

  • std::condition_variable
    該條件變量必須配合 std::unique_lock 使用
  • std::condition_variable_any
    可以和任何帶 lock, unlock 的 mutex 配合使用. 他更加通用, 更加靈活, 但是效率比前者差一些, 使用時會有一些額外的開銷

這兩者具有相同的成員函數

通知

  • notify_one
    喚醒一個阻塞于該條件變量的線程。 如果有多個等待的線程, 并沒有會優先喚醒誰的說法。即, 沒有喚醒順序, 是隨機的.

  • notify_all
    喚醒所有阻塞于該條件變量的線程

等待

  • wait
    讓當前線程阻塞直至條件變量被通知喚醒
  • wait_for
    導致當前線程阻塞直至通知條件變量、超過指定時間長度

  • 下面示例中, wait_for()最后一個參數是預制條件,調用 wait_for的時候,首先就會判斷這個條件,

    • 如果這個條件返回false,那么會繼續等待;
    • 如果在超時之前,收到了一個notify,那么他會再次執行這個預制條件來進行判斷,超時的時候也還會再次執行這個條件,這種可以用在處理隊列事件:
    // wait_for的第一個參數是鎖,第二個參數是時間長度,第三個參數是lambda表達式 cond_var.wait_for(lck, std::chrono::seconds(20), [] {std::this_thread::sleep_for(std::chrono::seconds(1));return false;});

    原文鏈接:https://blog.csdn.net/najiutan/article/details/110817106

  • wait_until
    導致當前線程阻塞直至通知條件變量、抵達指定時間點

  • wait_for需要相對時間(“等待長達10秒”),而wait_until需要絕對時間(“等到2012年10月30日12:00”)。
    比較時間參數的聲明:

    // wait_for: const std::chrono::duration<Rep, Period>& rel_time// wait_until: const std::chrono::time_point<Clock, Duration>& abs_time

    關于條件變量的詳細以及wait_for和wait_until用法可參考

    因為虛假喚醒的存在 和 為了避免丟失信號量 (避免丟失信號量就是在調用wait的時候, 在其之前發出的喚醒都不會對wait生效, 而系統不會保存這些條件變量, 調用完就丟掉了),
    我們必須使用循環判斷條件變量,所以我們使用條件變量必須結合 mutex ,并且將判斷條件放入 while 循環, 而不是使用 if。


    std::call_once

    <mutex>中還提供了std::call_once函數,保證某個函數即使在多個線程中同時調用時,也只被調用一次。使用 std::call_once 需要同時使用其幫助結構體 once_flag

    template< class Callable, class... Args > void call_once( std::once_flag& flag, Callable&& f, Args&&... args );
    • 如果調用call_once時flag已經被設置,說明函數f已經被調用過了,這種情況下call_once直接返回;
    • 如果flag未被設置,則調用call_once時會直接調用std?::?forward<Callable>(f),并向其傳遞std?::?forward<Args>(args)...參數。如果此時f內拋出了異常,則異常會傳遞給call_once的調用者,并且不會設置flag,這樣可以使得后續使用同一標志調用call_once時能繼續調用f函數。
    #include <iostream> #include <thread> #include <mutex>using namespace std;once_flag onlyOnce; mutex myMutex;void func() //線程函數 {myMutex.lock();cout << "here is func" << endl;myMutex.unlock();call_once(onlyOnce, []{ //僅僅調用一次cout << "hello world!" << endl;}); }int main() {thread t1(func);thread t2(func);thread t3(func);t1.join();t2.join();t3.join();return 0; }

    這篇博客算是拖了好幾個月才寫的了, 寫一半還沒了, 以后寫博客記得好好保存…

總結

以上是生活随笔為你收集整理的【多线程】C++11进行多线程开发 (std::thread)的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。