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

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

C++11 多线程库使用说明

發布時間:2023/12/10 c/c++ 34 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++11 多线程库使用说明 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
  • 多線程基礎
    1.1 進程與線程
    根本區別:
    進程是操作系統資源分配的基本單位,線程是任務調度和執行的基本單位
  • 開銷方面:
    每個進程都有自己獨立的代碼和數據空間,程序之間的切換開銷較大。
    線程可以看作是輕量級的進程,同一類線程共享代碼和數據空間,每個線程都有自己獨立的運行棧和程序計數器,線程之間切換開銷小。

    所處環境:
    一個操作系統能同時運行多個進程(程序)。
    在一個進程中,可以有多個線程同時執行。

    內存分配方面:
    系統在運行的時候會為每個進程分配不同的內存空間。
    對線程而言,系統不會為線程分配內存(線程使用的資源,來自于其所屬進程的資源),線程組之間只能共享資源。

    包含關系:
    沒有線程的進程可以看作是單線程。一個進程可以包含多個線程,每個進程有且只有一個主線程
    線程是進程的一部分,所以線程也稱為輕量級進程-

    1.2 并發與并行
    如果某個系統支持兩個或者多個動作(Action)同時存在,那么這個系統就是一個并發系統。如果某個系統支持兩個或者多個動作同時執行,那么這個系統就是一個并行系統。

    并行”概念是“并發”概念的一個子集。也就是說,你可以編寫一個擁有多個線程或者進程的并發程序,但如果沒有多核處理器來執行這個程序,那么就不能以并行方式來運行代碼。因此,凡是在求解單個問題時涉及多個執行流程的編程模式或者執行行為,都屬于并發編程的范疇。

    并發是不是一個線程,并行是多個線程?
    答:并發和并行都可以是多個線程,就看這些線程能不能同時被(多個)cpu(物理線程)執行,如果可以就是并行,而并發誓多個線程被 cpu 輪流切換著執行。

    進程并發
    線程并發
    1.3 進程通信
    同一臺PC:管道,文件,消息隊列,共享內存
    不同PC:socket
    1.4 線程通訊
    鎖機制:包括互斥鎖、條件變量、讀寫鎖
    互斥鎖提供了以排他方式防止數據結構被并發修改的方法。
    讀寫鎖允許多個線程同時讀共享數據,而對寫操作是互斥的。
    條件變量可以以原子的方式阻塞進程,直到某個特定條件為真為止。對條件的測試是在互斥鎖的保護下進行的。條件變量始終與互斥鎖一起使用。

    信號量機制(Semaphore)
    包括無名線程信號量和命名線程信號量。

    信號機制(Signal)
    類似進程間的信號處理。

    線程間的通信目的主要是用于線程同步,所以線程沒有像進程通信中的用于數據交換的通信機制。

    1.5 線程并發
    一個進程中的所有線程共享地址空間,因此全局變量,指針,引用可以在線程之間傳遞
    數據一致性問題
    線程之間切換需要保存中間量,消耗資源,所以線程不是越多越好,極限差不多在 2000 個線程,或者根據硬件,比如 CPU*2,或者根據業務需要,實際中一般不操作500個,控制在 200 個以內,否則效率太低。
    2. C++ 11 多線程庫
    c++ 11 開始語言本身提供多線程支持,因此可以實現跨平臺,可移植性。

    2.1 創建線程

    include

    初始函數,函數結束,他的所有線程結束
    主線程結束,它的子線程結束
    #include
    #include

    using namespace std;

    // 入口函數
    void entry(int a)
    {
    cout << “entry sub-thread” << endl;
    }

    int main()
    {
    thread threadObj(entry, 6); //入口函數,參數
    threadObj.join(); //等待線程結束
    // threadObj.detch();
    cout << “entry main thread” << endl;
    return 0;
    }

    類的成員函數作為入口函數

    class A {
    public:
    A(int i) : m_i(i)
    {
    cout << "construct! thread ID: " << std::this_thread::get_id() << endl;
    }

    void print(const A& a) {cout << "sub_thread ID: " << std::this_thread::get_id() << endl; } int m_i;

    };

    int main()
    {
    int n = 1;
    int& m = n;
    A a(10);
    cout << "main thread ID: " << std::this_thread::get_id() << endl;

    thread mythread(&A::print, &a, a); //傳入成員函數地址、 類對象地址、參數 mythread.join(); return 0;

    }

    2.2 thread 的入口
    可調用對象(普通函數,類成員函數,類靜態函數,仿函數,函數指針,重載了operate ()的類對象,lambda表達式,std::function)
    class 需要是可調用的類,即 void operator ()(), 注意以對象作為入口,對象會被復制到子線程中
    2.3 thread 入口參數
    引用 vs實測背后發生了copy,所以無法通過引用來傳值,需要注意的是,如果用引用需要同時用const,比如const A& a, 否則會報錯,如果需要通過引用傳值,需要用std::ref(或者加 & )
    void print(const int& n) //沒有const會報錯
    {
    cout << "sub_thread: " << n << endl;
    }

    int main()
    {
    int n = 1;
    int& m = n;

    thread mythread(print, m); //雖然print參數是引用,m 是引用但是會發生拷貝 //thread mythread(print, std::ref(m)) //引用 mythread.join(); return 0;

    }

    指針 不安全,可能主線程已經銷毀了內存,造成隱患,detach時一定會出問題

    臨時參數(對象)可以幫助解決主線程退出的問題,即主線程退出之前會先構造好臨時對象,具體來說,在創建線程時就構造臨時對象,然后在線程入口函數里面用引用來接(否則會多一次拷貝構造)

    如果用隱式類型轉換會有風險,因為隱式轉換會在子線程中完成,如果detach的話,就會線程不安全

    如果參數是智能指針,如unique_ptr, 需要用std::move(your unique_ptr), 但是一定要用join,因為內存是共享的,否則會不安全

    成員函數指針

    2.4 多個線程下保護共享數據
    2.4.1 mutex
    #include “stdafx.h”
    #include
    #include
    #include
    #include
    #include

    using namespace std;

    class Msg {
    public:
    void InMsg()
    {
    for(int i = 0; i < 1000; ++i)
    {
    cout << "start input msg id = " << i << endl;
    mut_Msg.lock();
    m_Msg.push_back(i);
    mut_Msg.unlock();
    cout << "end input msg id = " << i << endl;
    }
    }

    void OutMsg() {while (1){if (bOutMsg()){cout << "pop out msg success!" << endl;}else{cout << "msg box is empty!" << endl;_sleep(1000);}} }bool bOutMsg() {mut_Msg.lock();if (!m_Msg.empty()){m_Msg.pop_front();mut_Msg.unlock();return 1;}else{mut_Msg.unlock();return 0;} }

    private:
    list m_Msg;
    mutex mut_Msg;
    };

    int main(void)
    {
    Msg a;
    thread thread1(&Msg::InMsg, &a);
    thread thread2(&Msg::OutMsg, &a);
    thread1.join();
    thread2.join();
    return 0;
    }

    mutex 使用時應該盡量只保護需要保護的代碼段。
    unlock 不能丟
    2.4.2 lockguard
    可以用 lockguard 接管 mutex,這樣就不用手動 unlock, 傳入 std::adopt_lock 參數就是告訴 lockguard,鎖已經鎖了,只需要管理 unlock 就可以了。

    lockguard 實際上是在其構造函數中調用了 lock(), 析構函數中調用 unlock().

    std::lock(mutex1, mutex2);
    std::lockguardstd::mutex guard1(mutex1, std::adopt_lock)
    std::lockguardstd::mutex guard2(mutex2, std::adopt_lock)
    //…

    lockguard 沒有提供手動 lock & unlock 的接口。

    2.4.3 死鎖
    死鎖,兩個或以上的 lock 可能出現死鎖。

    threadA
    {
    mutexA.lock();
    mutexB.lock();
    //do some thing
    mutexA.unlock();
    mutexB.unlock();
    }

    threadB
    {
    mutexB.lock();
    mutexA.lock();
    //do some thing
    mutexA.unlock();
    mutexB.unlock();
    }

    解決方法:

    可以通過控制不同的線程lock的順序來避免
    std::lock() 同時鎖多個鎖,如果有一個鎖不上,它會 unlock 已經 lock 的鎖
    std::lock(mutex1, mutex2);
    //…
    mutex1.unlock();
    mutex2.unlock();

    2.4.4 unique_lock
    unique_lock 比 lockguard 更靈活,但是效率要低一些,占用內存更多。

    unique_lock 可以取代 lockguard,但是相比 lockguard,有更豐富的一些功能。

    unique_lock 支持以下參數:

    adopt_lock,意義和 lockguard 中一樣表示已經lock了
    try_to_lock, 意味著 lock 和 unlock 都自動管理,可以判斷是否lock成功,做不同操作,所以不會卡住。
    std::unique_lockstd::mutex guard1(mutex1, std::try_to_lock);
    if(guard1.owns_lock()) // get lock
    {
    //…
    }
    else
    {
    //…
    }

    defer_lock, 初始化一個沒有加鎖的 lock, 可以手動 lock,手動或者自動 unlock,
    手動 lock 和 unlock 可以直接調用 unique_lock 的成員函數:lock(), unlock()
    unique_lock 可以通過 release 來釋放資源,即不再關聯mutex。

    unique_lock 和 lock_guard 都不能復制,但是unique_lock 的所有權可以轉移。

    std::unique_lockstd::mutex guard1(_mu);
    std::unique_lockstd::mutex guard2 = guard1; // error
    std::unique_lockstd::mutex guard2 = std::move(guard1); // ok

    2.5 線程安全的單例模式
    #include
    #include
    #include

    using namespace std;

    std::mutex instance_mutex;

    class SP {
    private:
    SP() {}
    static SP *m_pInstance; //static使得 m_pInstance 的作用域到程序結束

    class FREE { //這個class專門負責 delete public:~FREE(){if (SP::m_pInstance){delete SP::m_pInstance;SP::m_pInstance = NULL;}} };

    public:
    static SP* GetInstance() //static,否則無法直接調用
    {
    if (m_pInstance == NULL) //雙重鎖定,提高運行效率,減少不必要的lock,unlock
    {
    std::unique_lockstd::mutex mutex1(instance_mutex); //c++ 11,自動lock,unlock
    if (m_pInstance == NULL)
    {
    m_pInstance = new SP();
    static FREE f; //static 表示作用域直到程序推出,也就是說程序退出時會調用析構函數,從而達到自動釋放內存的作用
    }
    return m_pInstance;
    }
    }

    static void Free() //手動 delete {if (m_pInstance){delete m_pInstance;m_pInstance = NULL;} }

    };

    SP* SP::m_pInstance = NULL;

    int main(void)
    {
    SP *p1 = SP::GetInstance();
    SP *p2 = SP::GetInstance();
    SP::Free();
    SP::Free();
    p1->Free();
    p2->Free();
    }

    2.6 call_once()
    std::call_once() 是 c++ 11 引入的函數,保證某個函數只執行一次,具備互斥量的功能,但是比 mutex 高效, 適合比如 init 等場合

    std::once_flag g_flag //決定 call_once 是否調用function

    std::call_once(g_flag, function_with_code_only_call_once)

    2.7 condition_variable
    利用 condition_variable 一般用來等待 unique_lock, 可以提高程序執行效率。

    condition_variable 類有三個成員函數

    wait() 等待一個條件成立
    notify_one() 隨機喚醒一個正在 wait 的線程,如果線程沒有阻塞在 wait() 處,則沒有辦法喚醒
    notify_all() 喚醒所有等待的線程
    //thread A
    std::unique_lockstd::mutex lock1(mutex1);

    //do some thing

    condition1.notify_one();
    //condition1.notify_all();

    //thread B
    std::unique_lockstd::mutex lock1(mutex1);

    //do some thing

    condition1.wait(lock1); //如果沒有第二個參數,wait將 release mutex1,然后阻塞,等待被喚醒
    //condition1.wait(lock1, [this]{ //第二個參數可以是任何可調用對象,如果表達式返回ture,直接return,如果為fasle,同上
    if(m_bStatus)
    {
    return true;
    }
    return false;
    })

    虛假喚醒
    線程被notify,但是卻并沒發執行,比如上面的例子,m_bStatus 為false, 所以通過第二個參數可以防止虛假喚醒。
    2.8 std::async, std::future
    之前通過 thread 來創建線程,如果需要返回結果,可以通過全局變量/引用來實現,這里是另一種方式。

    async 用于啟動一個異步任務(創建線程并執行入口函數),返回一個 future 對象。通過 future 對象的 get() 獲取入口函數返回的結果。

    int entry()
    {
    //。。。
    return 1;
    }

    int main()
    {
    std::future result = std::async(entry); //entry 開始執行
    //std::future result = std::async(std::launch::async, entry); //效果同上
    //std::future result = std::async(std::launch::deferred, entry); //延遲創建,等待 get/wait 才開始執行,如果沒有調用,不會創建子線程
    int re = reult.get(); //get() 時會等待 entry 執行完, get() 不能調用多次
    //result.wait(); //不獲取值返回值,等待線程
    }

    2.9 std::packaged_task
    包裝可調用對象,方便作為線程入口調用。

    int entry(int a)
    {
    //。。。
    return a;
    }

    int main()
    {
    std::packaged_task<int(int)> pt(entry); //pt 本身就是一個可調用對象,類似函數,可以直接 pt(10),調用
    std::thread thread1(std::ref(pt), 1);
    thread1.join()

    std::future<int> result = pt.get_future(); //result 保存返回結果,可以 get //。。。

    }

    2.10 std::promise
    可以通過promise在線程直接傳遞值,一個線程往 promise 對象中寫值,在其他線程中取值

    void entry(std::promise &prom, int a)
    {
    //。。。
    prom.set_value(result)
    return;
    }

    void entry(std::future & f)
    {
    //。。。
    result = f.get();
    return;
    }

    int main()
    {
    std::promise prom;
    std::thread t1(entry, std::ref(prom), 10);
    t1.join()

    std::future<int> result = prom.get_future(); //result 保存返回結果,可以 get//。。。

    }

    2.11 atomic
    atomic 作用和 mutex 類似,不同點是 mutex 針對一個代碼段,而 atomic 針對一個變量。
    atomic 操作相比 mutex 效率更高。

    int g_count = 0;
    //mutex
    void entry()
    {
    mutex1.lock();
    g_count++;
    mutex1.unlock;
    }

    std::atomic g_count = 0;
    //atomic
    void entry()
    {
    g_count++;
    }

    2.12 windows 臨界區
    windows 臨界區的概念和 mutex 類似。另外多次進入臨界區是OK的,但是需要調用對應次數的出臨界區。mutex 是不允許同一個線程中多次 lock 的。

    include <windows.h>

    CRITICAL_SECTION winsec
    InitializeCriticalSection(winsec) //使用前必須初始化

    EnterCriticalSection(&winsec);
    EnterCriticalSection(&winsec);
    //do some thing
    LeaveCriticalSection(&winsec);
    LeaveCriticalSection(&winsec);

    2.13 線程池
    #ifndef THREAD_POOL_H
    #define THREAD_POOL_H

    #include
    #include
    #include
    #include
    #include
    #include <condition_variable>
    #include
    #include
    #include

    // 線程池類
    class ThreadPool {
    public:
    // 構造函數,傳入線程數
    ThreadPool(size_t threads);
    // 入隊任務(傳入函數和函數的參數)
    template<class F, class… Args>
    auto enqueue(F&& f, Args&&… args)
    ->std::future<typename std::result_of<F(Args…)>::type>;
    // 一個最簡單的函數包裝模板可以這樣寫(C++11)適用于任何函數(變參、成員都可以)
    // template<class F, class… Args>
    // auto enqueue(F&& f, Args&&… args) -> decltype(declval()(declval()…))
    // { return f(args…); }
    // C++14更簡單
    // template<class F, class… Args>
    // auto enqueue(F&& f, Args&&… args)
    // { return f(args…); }

    // 析構 ~ThreadPool();

    private:
    // need to keep track of threads so we can join them
    // 工作線程組
    std::vector< std::thread > workers;
    // 任務隊列
    std::queue< std::function<void()> > tasks;

    // synchronization 異步 std::mutex queue_mutex; // 隊列互斥鎖 std::condition_variable condition; // 條件變量 bool stop; // 停止標志

    };

    // the constructor just launches some amount of workers
    // 構造函數僅啟動一些工作線程
    inline ThreadPool::ThreadPool(size_t threads)
    : stop(false)
    {
    for (size_t i = 0; i<threads; ++i)
    // 添加線程到工作線程組
    workers.emplace_back( // 與push_back類型,但性能更好(與此類似的還有emplace/emlace_front)
    [this]
    { // 線程內不斷的從任務隊列取任務執行
    for (;😉
    {
    std::function<void()> task;

    {// 拿鎖(獨占所有權式)std::unique_lock<std::mutex> lock(this->queue_mutex);// 等待條件成立this->condition.wait(lock,[this] { return this->stop || !this->tasks.empty(); });// 執行條件變量等待的時候,已經拿到了鎖(即lock已經拿到鎖,沒有阻塞)// 這里將會unlock釋放鎖,其他線程可以繼續拿鎖,但此處任然阻塞,等待條件成立// 一旦收到其他線程notify_*喚醒,則再次lock,然后進行條件判斷// 當[return this->stop || !this->tasks.empty()]的結果為false將阻塞// 條件為true時候解除阻塞。此時lock依然為鎖住狀態// 如果線程池停止或者任務隊列為空,結束返回if (this->stop && this->tasks.empty()) {return;}// 取得任務隊首任務(注意此處的std::move)task = std::move(this->tasks.front());// 從隊列移除this->tasks.pop();}// 執行任務task();} } );

    }

    // add new work item to the pool
    // 添加一個新的工作任務到線程池
    template<class F, class… Args>
    auto ThreadPool::enqueue(F&& f, Args&&… args)
    -> std::future<typename std::result_of<F(Args…)>::type>
    {
    using return_type = typename std::result_of<F(Args…)>::type;

    // 將任務函數和其參數綁定,構建一個packaged_task auto task = std::make_shared< std::packaged_task<return_type()> >(std::bind(std::forward<F>(f), std::forward<Args>(args)...)); // 獲取任務的future std::future<return_type> res = task->get_future(); {// 獨占拿鎖std::unique_lock<std::mutex> lock(queue_mutex);// don't allow enqueueing after stopping the pool// 不允許入隊到已經停止的線程池if (stop) {throw std::runtime_error("enqueue on stopped ThreadPool");}// 將任務添加到任務隊列tasks.emplace([task]() { (*task)(); }); } // 發送通知,喚醒某一個工作線程取執行任務 condition.notify_one(); return res;

    }

    // the destructor joins all threads
    inline ThreadPool::~ThreadPool()
    {
    {
    // 拿鎖
    std::unique_lockstd::mutex lock(queue_mutex);
    // 停止標志置true
    stop = true;
    }
    // 通知所有工作線程,喚醒后因為stop為true了,所以都會結束
    condition.notify_all();
    // 等待所有工作線程結束
    for (std::thread &worker : workers) {
    worker.join();
    }
    }

    #endif

    總結

    以上是生活随笔為你收集整理的C++11 多线程库使用说明的全部內容,希望文章能夠幫你解決所遇到的問題。

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