一個非同步函數是不是內核會單獨開一個線程來運行?
我試了一下 開了100個計時器 然後非同步等待3秒鐘 發現他們同時等待完然後執行回調函數 是不是應用程序發起非同步操作之後 操作系統會開一個線程來執行這個非同步函數 這樣才能讓這100個非同步等待操作是並發執行的 求指教!
代碼:
#include &
#include &
#include &
#include &#include &
using namespace boost::asio;int main()
{
io_service ioservice;steady_timer *timer_array[100];
for (int i = 0; i &< 100; ++i) { timer_array[i] = new steady_timer(ioservice, std::chrono::seconds(3)); timer_array[i]-&>async_wait([=](const boost::system::error_code /* e */)
{
std::cout &<&< "message from timer. " &<&< i &<&< " " &<&< std::this_thread::get_id() &<&< std::endl; std::cout &<&< "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" &<&< std::endl; }); } ioservice.run(); return 0; }
QueueUserWorkItem,QueueUserApc函數告訴你:不一定
從最外層來看非同步程序,它就是一個有限狀態機,io事件為輸入事件,回調是狀態轉移動作,每次用非同步函數設置回調的操作就是在修改狀態機,非同步框架幫助程序員維護了這樣一個有限狀態機。至於線程,它就是根據當前狀態機來執行狀態轉移過程。至於說並發,其實就是看起來像並發而已,就像單核cpu也可以跑並發多任務一樣。
對應到題主的程序,async_wait就是在修改狀態機(這裡實際上是增加了一個狀態),相當於給系統說,3秒後產生一個時鐘事件,產生時鐘事件後執行lambda回調,布置妥當後,調用io_service::run就開始執行該狀態機。整個過程就只有一個主線程。
最後,作為一個重試強迫症患者不得不說,題主的程序沒有釋放timer指針,這不能忍。這個看內核實現。就linux內核而言,有中斷,軟中斷,tasklet,worker線程。不同非同步調用實現方式並不一樣。就定時器來說,一般通過tasklet實現,而tasklet可能是線程,也可能不是線程,本身屬於軟中斷一種,如果嵌套層次少,會直接在中斷返回之前執行;否則在一個內核線程執行。worker是線程的,一般每個cpu會有一個單獨線程。不同啟動會有不同的線程,如網卡收發會有2個線程。如網卡接收數據,先通過軟中斷接收網卡數據,再喚醒接收worker線程進一步處理協議。
單線程,會有一個timer隊列,哪個timer超時就調用那個timer回調。
你寫的這個demo一般單線程足夠了。如果是Linux系統,io_service底層採用了epoll去實現非同步機制。(注意epoll本身不是非同步的)
從操作系統實現多任務管理的角度來看,開100個計時器相當於是往timer隊列中添加100個timer,每個timer的timeout都是3秒,3秒超時調遍歷timer隊列,把這100個timer所帶的data註冊到對應的data隊列中,在任務切換計時器每次超時的時候會調用中斷處理函數執行任務切換,因為每個data隊列中都有data,所以每一個task都會執行,否則task進入休眠狀態。也就是說cpu同時在這100個線程隊列切換,所以看起來就是同時等待3秒然後同時開始執行。
你這個代碼沒有開線程呀。io_service不會主動創建線程的。你的所有代碼都是在主線程跑的。
就題目的例子來說,只有主線程調用ioservices.run(),即只有一個工作線程。你如果不執行ioservices.run(),計時器不會工作,回調也不會被執行。
你可以在回調中下斷點,看看每次回調函數執行時,當前的線程id即是主線程的id。之所以等待3秒後你的100次同時返回,是因為你每個回調里只做了列印動作,執行速度快。看起來是3秒後一瞬間全部列印出來結果了。是不是應用程序發起非同步操作之後 操作系統會開一個線程來執行這個非同步函數 這樣才能讓這100個非同步等待操作是並發執行的
我的看法是應用程序發起非同步操作,只是加入到io_services的一個timer隊列中,在你ioservices.run()時,開始計算timer並等待超時,時間一到執行最早的timer(你的timer全部一樣,所以執行第一個),執行完後,看看timer隊列,還是滿足時間,接著就執行下一個回調函數。
所以並不是「並發」執行的,而是順序執行的。 如果你使用多個線程調用ioservices.run(),則各個回調被分配給不同的線程去執行,這樣就是「並發」(不嚴格追究並發的含義)的。可以研究boostasiodetailimpl目錄中win_iocp_io_service.ipp中win_iocp_io_service::do_one的實現。其中這個文件中的timer_queues_是跟timer隊列有關的。
推薦閱讀: