1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        現(xiàn)代 C++ 并發(fā)編程基礎(chǔ)

        2021-10-29 00:24

        并行基礎(chǔ)


        std::thread?用于創(chuàng)建一個(gè)執(zhí)行的線程實(shí)例,所以它是一切并發(fā)編程的基礎(chǔ),使用時(shí)需要包含??頭文件, 它提供了很多基本的線程操作,例如?get_id()?來(lái)獲取所創(chuàng)建線程的線程 ID,使用?join()?來(lái)加入一個(gè)線程等等,例如:

        #include 
        #include

        int main() {
        std::thread t([](){
        std::cout << "hello world." << std::endl;
        });
        t.join();
        return 0;
        }


        互斥量與臨界區(qū)


        我們?cè)诓僮飨到y(tǒng)、亦或是數(shù)據(jù)庫(kù)的相關(guān)知識(shí)中已經(jīng)了解過(guò)了有關(guān)并發(fā)技術(shù)的基本知識(shí),mutex 就是其中的核心之一。C++11 引入了?mutex?相關(guān)的類,其所有相關(guān)的函數(shù)都放在??頭文件中。


        std::mutex?是 C++11 中最基本的?mutex?類,通過(guò)實(shí)例化?std::mutex?可以創(chuàng)建互斥量, 而通過(guò)其成員函數(shù) lock()?可以進(jìn)行上鎖,unlock()?可以進(jìn)行解鎖。


        但是在實(shí)際編寫(xiě)代碼的過(guò)程中,最好不去直接調(diào)用成員函數(shù), 因?yàn)檎{(diào)用成員函數(shù)就需要在每個(gè)臨界區(qū)的出口處調(diào)用 unlock(),當(dāng)然,還包括異常。


        這時(shí)候 C++11 還為互斥量提供了一個(gè) RAII 語(yǔ)法的模板類 std::lock_guard。RAII 在不失代碼簡(jiǎn)潔性的同時(shí),很好的保證了代碼的異常安全性。


        在 RAII 用法下,對(duì)于臨界區(qū)的互斥量的創(chuàng)建只需要在作用域的開(kāi)始部分,例如:

        #include 
        #include

        int v = 1;

        void critical_section(int change_v) {
        static std::mutex mtx;
        std::lock_guard<std::mutex> lock(mtx);

        // 執(zhí)行競(jìng)爭(zhēng)操作
        v = change_v;

        // 離開(kāi)此作用域后 mtx 會(huì)被釋放
        }

        int main() {
        std::thread t1(critical_section, 2), t2(critical_section, 3);
        t1.join();
        t2.join();

        std::cout << v << std::endl;
        return 0;
        }

        由于 C++ 保證了所有棧對(duì)象在生命周期結(jié)束時(shí)會(huì)被銷(xiāo)毀,所以這樣的代碼也是異常安全的。無(wú)論 critical_section()?正常返回、還是在中途拋出異常,都會(huì)引發(fā)堆?;赝?,也就自動(dòng)調(diào)用了 unlock()。


        而?std::unique_lock?則相對(duì)于?std::lock_guard?出現(xiàn)的,std::unique_lock?更加靈活,?std::unique_lock?的對(duì)象會(huì)以獨(dú)占所有權(quán)(沒(méi)有其他的 unique_lock 對(duì)象同時(shí)擁有某個(gè) mutex 對(duì)象的所有權(quán)) 的方式管理 mutex 對(duì)象上的上鎖和解鎖的操作。


        所以在并發(fā)編程中,推薦使用 std::unique_lock。


        std::lock_guard?不能顯式的調(diào)用?lock?和?unlock, 而?std::unique_lock?可以在聲明后的任意位置調(diào)用, 可以縮小鎖的作用范圍,提供更高的并發(fā)度。


        如果你用到了條件變量 std::condition_variable::wait 則必須使用 std::unique_lock 作為參數(shù)。


        例如:

        #include 
        #include

        int v = 1;

        void critical_section(int change_v) {
        static std::mutex mtx;
        std::unique_lock<std::mutex> lock(mtx);
        // 執(zhí)行競(jìng)爭(zhēng)操作
        v = change_v;
        std::cout << v << std::endl;
        // 將鎖進(jìn)行釋放
        lock.unlock();

        // 在此期間,任何人都可以搶奪 v 的持有權(quán)

        // 開(kāi)始另一組競(jìng)爭(zhēng)操作,再次加鎖
        lock.lock();
        v += 1;
        std::cout << v << std::endl;
        }

        int main() {
        std::thread t1(critical_section, 2), t2(critical_section, 3);
        t1.join();
        t2.join();
        return 0;
        }


        期物


        期物(Future)表現(xiàn)為?std::future,它提供了一個(gè)訪問(wèn)異步操作結(jié)果的途徑,這句話很不好理解。為了理解這個(gè)特性,我們需要先理解一下在 C++11 之前的多線程行為。


        試想,如果我們的主線程 A 希望新開(kāi)辟一個(gè)線程 B 去執(zhí)行某個(gè)我們預(yù)期的任務(wù),并返回我一個(gè)結(jié)果。


        而這時(shí)候,線程 A 可能正在忙其他的事情,無(wú)暇顧及 B 的結(jié)果, 所以我們會(huì)很自然的希望能夠在某個(gè)特定的時(shí)間獲得線程 B 的結(jié)果。


        在 C++11 的 std::future 被引入之前,通常的做法是:創(chuàng)建一個(gè)線程 A,在線程 A 里啟動(dòng)任務(wù) B,當(dāng)準(zhǔn)備完畢后發(fā)送一個(gè)事件,并將結(jié)果保存在全局變量中。


        而主函數(shù)線程 A 里正在做其他的事情,當(dāng)需要結(jié)果的時(shí)候,調(diào)用一個(gè)線程等待函數(shù)來(lái)獲得執(zhí)行的結(jié)果。


        而 C++11 提供的?std::future?簡(jiǎn)化了這個(gè)流程,可以用來(lái)獲取異步任務(wù)的結(jié)果。自然地,我們很容易能夠想象到把它作為一種簡(jiǎn)單的線程同步手段,即屏障(barrier)。


        為了看一個(gè)例子,我們這里額外使用 std::packaged_task,它可以用來(lái)封裝任何可以調(diào)用的目標(biāo),從而用于實(shí)現(xiàn)異步的調(diào)用。舉例來(lái)說(shuō):

        #include 
        #include
        #include

        int main() {
        // 將一個(gè)返回值為7的 lambda 表達(dá)式封裝到 task 中
        // std::packaged_task 的模板參數(shù)為要封裝函數(shù)的類型
        std::packaged_task<int()> task([](){return 7;});
        // 獲得 task 的期物
        std::future<int> result = task.get_future(); // 在一個(gè)線程中執(zhí)行 task
        std::thread(std::move(task)).detach();
        std::cout << "waiting...";
        result.wait(); // 在此設(shè)置屏障,阻塞到期物的完成
        // 輸出執(zhí)行結(jié)果
        std::cout << "done!" << std:: endl << "future result is " << result.get() << std::endl;
        return 0;
        }

        在封裝好要調(diào)用的目標(biāo)后,可以使用?get_future()?來(lái)獲得一個(gè)?std::future?對(duì)象,以便之后實(shí)施線程同步。


        條件變量


        條件變量?std::condition_variable?是為了解決死鎖而生,當(dāng)互斥操作不夠用而引入的。比如,線程可能需要等待某個(gè)條件為真才能繼續(xù)執(zhí)行, 而一個(gè)忙等待循環(huán)中可能會(huì)導(dǎo)致所有其他線程都無(wú)法進(jìn)入臨界區(qū)使得條件為真時(shí),就會(huì)發(fā)生死鎖。


        所以,condition_variable 實(shí)例被創(chuàng)建出現(xiàn)主要就是用于喚醒等待線程從而避免死鎖。?


        std::condition_variable的 notify_one()?用于喚醒一個(gè)線程;notify_all()?則是通知所有線程。下面是一個(gè)生產(chǎn)者和消費(fèi)者模型的例子:

        #include 
        #include
        #include
        #include
        #include
        #include


        int main() {
        std::queue<int> produced_nums;
        std::mutex mtx;
        std::condition_variable cv;
        bool notified = false; // 通知信號(hào)

        // 生產(chǎn)者
        auto producer = [&]() {
        for (int i = 0; ; i++) {
        std::this_thread::sleep_for(std::chrono::milliseconds(900));
        std::unique_lock<std::mutex> lock(mtx);
        std::cout << "producing " << i << std::endl;
        produced_nums.push(i);
        notified = true;
        cv.notify_all(); // 此處也可以使用 notify_one
        }
        };
        // 消費(fèi)者
        auto consumer = [&]() {
        while (true) {
        std::unique_lock<std::mutex> lock(mtx);
        while (!notified) { // 避免虛假喚醒
        cv.wait(lock);
        }
        // 短暫取消鎖,使得生產(chǎn)者有機(jī)會(huì)在消費(fèi)者消費(fèi)空前繼續(xù)生產(chǎn)
        lock.unlock();
        std::this_thread::sleep_for(std::chrono::milliseconds(1000)); // 消費(fèi)者慢于生產(chǎn)者
        lock.lock();
        while (!produced_nums.empty()) {
        std::cout << "consuming " << produced_nums.front() << std::endl;
        produced_nums.pop();
        }
        notified = false;
        }
        };

        // 分別在不同的線程中運(yùn)行
        std::thread p(producer);
        std::thread cs[2];
        for (int i = 0; i < 2; ++i) {
        cs[i] = std::thread(consumer);
        }
        p.join();
        for (int i = 0; i < 2; ++i) {
        cs[i].join();
        }
        return 0;
        }

        值得一提的是,在生產(chǎn)者中我們雖然可以使用 notify_one(),但實(shí)際上并不建議在此處使用, 因?yàn)樵诙嘞M(fèi)者的情況下,我們的消費(fèi)者實(shí)現(xiàn)中簡(jiǎn)單放棄了鎖的持有,這使得可能讓其他消費(fèi)者 爭(zhēng)奪此鎖,從而更好的利用多個(gè)消費(fèi)者之間的并發(fā)。


        話雖如此,但實(shí)際上因?yàn)?std::mutex 的排他性, 我們根本無(wú)法期待多個(gè)消費(fèi)者能真正意義上的并行消費(fèi)隊(duì)列的中生產(chǎn)的內(nèi)容,我們?nèi)孕枰6雀?xì)的手段。


        來(lái)源:https://changkun.de/modern-cpp/zh-cn/07-thread/index.html


        推薦:

        Android FFmpeg 實(shí)現(xiàn)帶濾鏡的微信小視頻錄制功能

        全網(wǎng)最全的 Android 音視頻和 OpenGL ES 干貨,都在這了

        一文掌握 YUV 圖像的基本處理

        一文掌握 C++ 智能指針的使用

        瀏覽 85
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            日本操比视频 | 男人和女人在做性视频 | 国产永久免费黄色精品 | 欧美日韩亚洲中文字幕一区二区三区 | 91久久国产综合张津瑜 | 娇喘呻吟啊~啊~嗯~四合院 | 深爱丁香网 | 日韩欧美A片 | 欧美韩日在线 | www.青青操 |