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

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 人文社科 > 生活经验 >内容正文

生活经验

【C++】多线程与异步编程【四】

發(fā)布時(shí)間:2023/11/27 生活经验 32 豆豆
生活随笔 收集整理的這篇文章主要介紹了 【C++】多线程与异步编程【四】 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

文章目錄

  • 【C++】多線程與異步編程【四】
      • 0.三問
      • 1.什么是異步編程?
      • 1.1同步與異步
      • 1.2 **阻塞與非阻塞**
      • 2、如何使用異步編程
      • 2.1 使用全局變量與條件變量傳遞結(jié)果
        • 實(shí)例1:
      • 2.2 使用promise與future傳遞結(jié)果
        • 實(shí)例2
        • 實(shí)例3
      • 2.3使用packaged_task與future傳遞結(jié)果
        • 實(shí)例4
      • 2.4 使用async傳遞結(jié)果
        • 實(shí)例5
      • 3.小結(jié)

【C++】多線程與異步編程【四】

0.三問

同步,異步,多線程之間是什么關(guān)系?異步比同步高效在哪?多線程比單線程高效在哪? 捋一下, 想一下怎么回答。

1.什么是異步編程?

1.1同步與異步

前面談到并發(fā),互斥鎖與條件變量, 前面提到的線程同步主要是為了解決對(duì)共享數(shù)據(jù)的競(jìng)爭(zhēng)訪問問題,所以線程同步主要是對(duì)共享數(shù)據(jù)的訪問同步化(按照既定的先后次序,一個(gè)訪問需要阻塞等待前一個(gè)訪問完成后才能開始)。這篇文章談到的異步編程主要是針對(duì)任務(wù)或線程的執(zhí)行順序,也即一個(gè)任務(wù)不需要阻塞等待上一個(gè)任務(wù)執(zhí)行完成后再開始執(zhí)行,程序的執(zhí)行順序與任務(wù)的排列順序是不一致的。下面從任務(wù)執(zhí)行順序的角度解釋下同步與異步的區(qū)別:

  • 同步:就是在發(fā)出一個(gè)調(diào)用時(shí),在沒有得到結(jié)果之前,該調(diào)用就不返回。但是一旦調(diào)用返回,就得到返回值了。換句話說,就是由調(diào)用者主動(dòng)等待這個(gè)調(diào)用的結(jié)果。
  • 異步:調(diào)用在發(fā)出之后,這個(gè)調(diào)用就直接返回了,所以沒有返回結(jié)果。換句話說,當(dāng)一個(gè)異步過程調(diào)用發(fā)出后,調(diào)用者不會(huì)立刻得到結(jié)果。 而是在調(diào)用發(fā)出之后,被調(diào)用者通過“狀態(tài)”、“通知”、“回調(diào)”三種途徑通知調(diào)用者。

可以使用哪一種途徑依賴于被帶調(diào)用者的實(shí)現(xiàn),除非被調(diào)用者提供多種選擇,否則不受調(diào)用者控制。如果被調(diào)用者用狀態(tài)來通知,那么調(diào)用者就需要每隔一定時(shí)間檢查一次,效率就很低。如果使用通知和回調(diào)的方式,效率則很高。因?yàn)楸徽{(diào)用者幾乎不需要做額外的操作。

舉個(gè)栗子:
你打電話問書店老板有沒有《程序員的自我修養(yǎng)》這本書,如果是同步通信機(jī)制,書店老板會(huì)說,你稍等,”我查一下",然后開始查啊查,等查好了(可能是5秒,也可能是一天)告訴你結(jié)果(返回結(jié)果)。而異步通信機(jī)制,書店老板直接告訴你我查一下啊,查好了打電話給你,然后直接掛電話了(不返回結(jié)果)。然后查好了,他會(huì)主動(dòng)打電話給你。在這里老板通過“回電”這種方式來回調(diào)。

1.2 阻塞與非阻塞

阻塞和非阻塞關(guān)注的是程序在等待調(diào)用結(jié)果(消息,返回值)時(shí)的狀態(tài)。

阻塞調(diào)用是指調(diào)用結(jié)果返回之前,當(dāng)前線程會(huì)被掛起。調(diào)用線程只有在得到結(jié)果之后才會(huì)返回。

非阻塞調(diào)用指在不能立刻得到結(jié)果之前,該調(diào)用不會(huì)阻塞當(dāng)前線程。

舉個(gè)栗子:

愛喝茶的老張,有兩把水壺(普通水壺,簡(jiǎn)稱水壺;會(huì)響的水壺,簡(jiǎn)稱響水壺)。

  1. 老張把水壺放到火上,立等水開,然后泡茶。(同步阻塞)老張覺得自己有點(diǎn)傻。
  2. 老張把水壺放到火上,去客廳看電視,時(shí)不時(shí)去廚房看看水開沒有,然后泡茶。(同步非阻塞)。
  3. 老張還是覺得自己有點(diǎn)傻,于是變高端了,買了把會(huì)響笛的那種水壺。水開后,能大聲發(fā)出嘀~~~的噪音。
  4. 老張把響水壺放到火上,立等水開泡茶。(異步阻塞)。
  5. 老張覺得這樣傻等意義不大,老張把響水壺放到火上,去客廳看電視,水壺響后泡茶。(異步非阻塞)。
  • 這里所謂同步異步,只是對(duì)于事件燒水和事件看電視。普通水壺,同步,指水壺?zé)?#xff08;線程)和來張(主線程)同時(shí)開始;響水壺,異步。雖然都能干活,但響水壺可以在自己完工之后,燒水事件,提示老張(主線程)水開了。這是普通水壺所不能及的。同步只能讓調(diào)用者去輪詢自己(情況2中),造成老張效率的低下。
  • 所謂阻塞非阻塞,僅僅對(duì)于老張的狀態(tài)(主線程)而言。立等的老張,阻塞,主線程不能進(jìn)行其他工作;看電視的老張,非阻塞,主線程可以進(jìn)行其他工作。
    情況1和情況3中老張就是阻塞的,主線程(老張的狀態(tài))不能去做其他任何事情,媳婦喊他都不知道。雖然4中響水壺是異步的,可對(duì)于立等的老張沒有太大的意義。所以一般異步是配合非阻塞使用的,這樣才能發(fā)揮異步的效用。

這里針對(duì)多核的CPU在阻塞的情況下,計(jì)算量比較大的情況下,可以采用異步的方法,在將第i次的燒水和泡茶采用異步實(shí)現(xiàn),使得喝茶這個(gè)目標(biāo)的實(shí)現(xiàn)可以更加的高效。

2、如何使用異步編程

在線程庫< thread >中并沒有獲得線程執(zhí)行結(jié)果的方法,通常情況下,線程調(diào)用者需要獲得線程的執(zhí)行結(jié)果或執(zhí)行狀態(tài),以便后續(xù)任務(wù)的執(zhí)行。那么,通過什么方式獲得被調(diào)用者的執(zhí)行結(jié)果或狀態(tài)呢?

2.1 使用全局變量與條件變量傳遞結(jié)果

前面談到的條件變量具有“通知–喚醒”功能,可以把執(zhí)行結(jié)果或執(zhí)行狀態(tài)放入一個(gè)全局變量中,當(dāng)被調(diào)用者執(zhí)行完任務(wù)后,通過條件變量通知調(diào)用者結(jié)果或狀態(tài)已更新,可以使用了。

實(shí)例1:

//future1.cpp 使用全局變量傳遞被調(diào)用線程返回結(jié)果,使用條件變量通知調(diào)用線程已獲得結(jié)果#include <vector>
#include <numeric>
#include <iostream>
#include <chrono>
#include <thread>
#include <mutex>
#include <condition_variable>int res = 0;						//保存結(jié)果的全局變量
std::mutex mu;						//互斥鎖全局變量
std::condition_variable cond;       //全局條件變量void accumulate(std::vector<int>::iterator first,std::vector<int>::iterator last)
{int sum = std::accumulate(first, last, 0);      //標(biāo)準(zhǔn)庫求和函數(shù)std::unique_lock<std::mutex> locker(mu);res = sum;locker.unlock();cond.notify_one();              // 向一個(gè)等待線程發(fā)出“條件已滿足”的通知
}int main()
{std::vector<int> numbers = { 1, 2, 3, 4, 5, 6 };std::thread work_thread(accumulate, numbers.begin(), numbers.end());std::unique_lock<std::mutex> locker(mu);//如果條件變量被喚醒,檢查結(jié)果是否被改變,為真則直接返回,為假則繼續(xù)等待cond.wait(locker, [](){ return res;});   std::cout << "result=" << res << '\n';locker.unlock();work_thread.join();//阻塞等待線程執(zhí)行完成getchar();return 0;
}
result=21

從上面的代碼可以看出,雖然也實(shí)現(xiàn)了獲取異步任務(wù)執(zhí)行結(jié)果的功能,但需要的全局變量較多,多線程間的耦合度也較高,編寫復(fù)雜程序時(shí)容易引入bug。有沒有更好的方式實(shí)現(xiàn)異步編程呢?C++ 11新增了一個(gè)< future >庫函數(shù)為異步編程提供了很大的便利。

2.2 使用promise與future傳遞結(jié)果

< future >頭文件功能允許對(duì)特定提供者設(shè)置的值進(jìn)行異步訪問,可能在不同的線程中。
這些提供程序(要么是promise 對(duì)象,要么是packaged_task對(duì)象,或者是對(duì)異步的調(diào)用async)與future對(duì)象共享共享狀態(tài):提供者使共享狀態(tài)就緒的點(diǎn)與future對(duì)象訪問共享狀態(tài)的點(diǎn)同步。< future >頭文件的結(jié)構(gòu)如下:

詳細(xì)資料見此

實(shí)例2

std::promise< T >構(gòu)造時(shí),產(chǎn)生一個(gè)未就緒的共享狀態(tài)(包含存儲(chǔ)的T值和是否就緒的狀態(tài))。可設(shè)置T值,并讓狀態(tài)變?yōu)閞eady。也可以通過產(chǎn)生一個(gè)future對(duì)象獲取到已就緒的共享狀態(tài)中的T值。繼續(xù)使用上面的程序示例,改為使用promise傳遞結(jié)果,修改后的代碼如下:

實(shí)例3

//future2.cpp 使用promise傳遞被調(diào)用線程返回結(jié)果,通過共享狀態(tài)變化通知調(diào)用線程已獲得結(jié)果#include <vector>
#include <thread>
#include <future>
#include <numeric>
#include <iostream>
#include <chrono>void accumulate(std::vector<int>::iterator first,std::vector<int>::iterator last,std::promise<int> accumulate_promise)
{int sum = std::accumulate(first, last, 0);accumulate_promise.set_value(sum);  // 將結(jié)果存入,并讓共享狀態(tài)變?yōu)榫途w以提醒future
}int main()
{// 演示用 promise<int> 在線程間傳遞結(jié)果。std::vector<int> numbers = { 1, 2, 3, 4, 5, 6 };std::promise<int> accumulate_promise;std::future<int> accumulate_future = accumulate_promise.get_future();std::thread work_thread(accumulate, numbers.begin(), numbers.end(),std::move(accumulate_promise));accumulate_future.wait();  //等待結(jié)果std::cout << "result=" << accumulate_future.get() << '\n';work_thread.join();  //阻塞等待線程執(zhí)行完成getchar();return 0;
}
result=21

std::promise< T >對(duì)象的成員函數(shù)get_future()產(chǎn)生一個(gè)std::future< T >對(duì)象,代碼示例中已經(jīng)展示了future對(duì)象的兩個(gè)方法:wait()與get(),下面給出更多操作函數(shù)供參考:

值得注意的是,std::future< T >在多個(gè)線程等待時(shí),只有一個(gè)線程能獲取等待結(jié)果。當(dāng)需要多個(gè)線程等待相同的事件的結(jié)果(即多處訪問同一個(gè)共享狀態(tài)),需要用std::shared_future< T >來替代std::future < T >,std::future< T >也提供了一個(gè)將future轉(zhuǎn)換為shared_future的方法f.share(),但轉(zhuǎn)換后原future狀態(tài)失效。這有點(diǎn)類似于智能指針std::unique_ptr< T >與std::shared_ptr< T >的關(guān)系,使用時(shí)需要留心。

2.3使用packaged_task與future傳遞結(jié)果

除了為一個(gè)任務(wù)或線程提供一個(gè)包含共享狀態(tài)的變量,還可以直接把共享狀態(tài)包裝進(jìn)一個(gè)任務(wù)或線程中。這就需要借助std::packaged_task< Func >來實(shí)現(xiàn)了,其具體用法如下:

[外鏈圖片轉(zhuǎn)存失敗,源站可能有防盜鏈機(jī)制,建議將圖片保存下來直接上傳(img-CpRCBIyA-1609675314863)(C:\Users\guoqi\AppData\Roaming\Typora\typora-user-images\1609669883139.png)]

std::packaged_task< Func >構(gòu)造時(shí)綁定一個(gè)函數(shù)對(duì)象,也產(chǎn)生一個(gè)未就緒的共享狀態(tài)。通過thread啟動(dòng)或者仿函數(shù)形式啟動(dòng)該函數(shù)對(duì)象。但是相比promise,沒有提供set_value()公用接口,而是當(dāng)執(zhí)行完綁定的函數(shù)對(duì)象,其執(zhí)行結(jié)果返回值或所拋異常被存儲(chǔ)于能通過 std::future 對(duì)象訪問的共享狀態(tài)中。繼續(xù)使用上面的程序示例,改為使用packaged_task傳遞結(jié)果,修改后的代碼如下:

實(shí)例4

//future3.cpp 使用packaged_task傳遞被調(diào)用線程返回結(jié)果,通過共享狀態(tài)變化通知調(diào)用線程已獲得結(jié)果#include <vector>
#include <thread>
#include <future>
#include <numeric>
#include <iostream>
#include <chrono>int accumulate(std::vector<int>::iterator first,std::vector<int>::iterator last)
{int sum = std::accumulate(first, last, 0);return sum;
}int main()
{// 演示用 packaged_task 在線程間傳遞結(jié)果。std::vector<int> numbers = { 1, 2, 3, 4, 5, 6 };std::packaged_task<int(std::vector<int>::iterator,std::vector<int>::iterator)> accumulate_task(accumulate);std::future<int> accumulate_future = accumulate_task.get_future();std::thread work_thread(std::move(accumulate_task), numbers.begin(), numbers.end());accumulate_future.wait();  //等待結(jié)果std::cout << "result=" << accumulate_future.get() << '\n';work_thread.join();  //阻塞等待線程執(zhí)行完成getchar();return 0;
}
result=21

一般不同函數(shù)間傳遞數(shù)據(jù)時(shí),主要是借助全局變量、返回值、函數(shù)參數(shù)等來實(shí)現(xiàn)的。上面第一種方法使用全局變量傳遞數(shù)據(jù),會(huì)使得不同函數(shù)間的耦合度較高,不利于模塊化編程。后面兩種方法分別通過函數(shù)參數(shù)與返回值來傳遞數(shù)據(jù),可以降低函數(shù)間的耦合度,使編程和維護(hù)更簡(jiǎn)單快捷。

2.4 使用async傳遞結(jié)果

前面介紹的std::promise< T >與std::packaged_task< Func >已經(jīng)提供了較豐富的異步編程工具,但在使用時(shí)既需要?jiǎng)?chuàng)建提供共享狀態(tài)的對(duì)象(promise與packaged_task),又需要?jiǎng)?chuàng)建訪問共享狀態(tài)的對(duì)象(future與shared_future),還是覺得使用起來不夠方便。有沒有更簡(jiǎn)單的異步編程工具呢?future頭文件也確實(shí)封裝了更高級(jí)別的函數(shù)std::async,其具體用法如下:

  • std::future std::async(std::launch policy, Func, Args…)

std::async是一個(gè)函數(shù)而非類模板,其函數(shù)執(zhí)行完后的返回值綁定給使用std::async的std::futrue對(duì)象(std::async其實(shí)是封裝了thread,packged_task的功能,使異步執(zhí)行一個(gè)任務(wù)更為方便)。Func是要調(diào)用的可調(diào)用對(duì)象(function, member function, function object, lambda),Args是傳遞給Func的參數(shù),std::launch policy是啟動(dòng)策略,它控制std::async的異步行為,我們可以用三種不同的啟動(dòng)策略來創(chuàng)建std::async:

  • std::launch::async參數(shù) 保證異步行為,即傳遞函數(shù)將在單獨(dú)的線程中執(zhí)行;
  • std::launch::deferred參數(shù) 當(dāng)其他線程調(diào)用get()/wait()來訪問共享狀態(tài)時(shí),將調(diào)用非異步行為;
  • std::launch::async | std::launch::deferred參數(shù) 是默認(rèn)行為(可省略)。有了這個(gè)啟動(dòng)策略,它可以異步運(yùn)行或不運(yùn)行,這取決于系統(tǒng)的負(fù)載。

繼續(xù)使用上面的程序示例,改為使用std::async傳遞結(jié)果,修改后的代碼如下:

實(shí)例5

//future4.cpp 使用async傳遞被調(diào)用線程返回結(jié)果#include <vector>
#include <thread>
#include <future>
#include <numeric>
#include <iostream>
#include <chrono>int accumulate(std::vector<int>::iterator first,std::vector<int>::iterator last)
{int sum = std::accumulate(first, last, 0);return sum;
}int main()
{// 演示用 async 在線程間傳遞結(jié)果。std::vector<int> numbers = { 1, 2, 3, 4, 5, 6 };auto accumulate_future = std::async(std::launch::async, accumulate, numbers.begin(), numbers.end());		//auto可以自動(dòng)推斷變量的類型std::cout << "result=" << accumulate_future.get() << '\n';getchar();return 0;
}
result=21

從上面的代碼可以看出使用std::async能在很大程度上簡(jiǎn)少編程工作量,使我們不用關(guān)注線程創(chuàng)建內(nèi)部細(xì)節(jié),就能方便的獲取異步執(zhí)行狀態(tài)和結(jié)果,還可以指定線程創(chuàng)建策略。所以,我們可以使用std::async替代線程的創(chuàng)建,讓它成為我們做異步操作的首選。

此外,還有什么機(jī)制可以通過底層實(shí)現(xiàn),提高性能,解決鎖機(jī)制的問題,下面將會(huì)學(xué)習(xí)基于原子數(shù)據(jù)類型和對(duì)應(yīng)的原子操作無鎖編程的的思想。

3.小結(jié)

  1. 多線程比單線程高效的原因就是利用了CPU的多核計(jì)算把一個(gè)大的任務(wù)分而治之從而加速任務(wù)計(jì)算。

  2. 異步比同步高效的原因是前者釋放了調(diào)用線程,讓調(diào)用線程可以做更多的事情而不至于被windows強(qiáng)制休眠浪費(fèi)線程資源。

  3. 就能方便的獲取異步執(zhí)行狀態(tài)和結(jié)果,還可以指定線程創(chuàng)建策略。所以,我們可以使用std::async替代線程的創(chuàng)建,讓它成為我們做異步操作的首選。

  4. 此外,還有什么機(jī)制可以通過底層實(shí)現(xiàn),提高性能,解決鎖機(jī)制的問題,下面將會(huì)學(xué)習(xí)基于原子數(shù)據(jù)類型和對(duì)應(yīng)的原子操作無鎖編程的的思想。

總結(jié)

以上是生活随笔為你收集整理的【C++】多线程与异步编程【四】的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。