boost::asio::deadline_timer(理解)


並發與並行: 並發和並行從宏觀上來講都是同時處理多路請求的概念。但並發和並行又有區別,並行是指兩個或者多個事件在同一時刻發生;而並發是指兩個或多個事件在同一時間間隔內發生。

1.Timer.1 - 使用同步定時器 

先完整介紹一下,后面的例子該省略的就省略了。

 

所有的Asio類只要簡單的包含"asio.hpp"頭文件便可使用:

#include <boost/asio.hpp>

因為本程序中使用了定時器,我們需要包含相應的的Boost.Date_Time 頭文件來處理時間操作:

#include <boost/date_time/posix_time/posix_time.hpp>

使用Asio的所有程序都至少需要一個提供訪問I/O功能的io_service 對象。因此在主函數中我們做的第一件事就是聲明一個這個類型的對象:

boost::asio::io_service io;

接下來我們聲明一個boost::asio::deadline_timer類型的對象。作為 Asio的核心類,它提供的I/O功能(在此為定時器功能)通常用一個io_service 的引用作為其構造函數的第一個參數。第二個參數設置一個從現在開始5秒后終止的定時器。

boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));

可以看一下boost::asio::deadline_timer的幾個構造函數

 1 basic_deadline_timer(  
 2     boost::asio::io_service & io_service);  
 3   
 4 basic_deadline_timer(  
 5     boost::asio::io_service & io_service,  
 6     const time_type & expiry_time);  
 7   
 8 basic_deadline_timer(  
 9     boost::asio::io_service & io_service,  
10     const duration_type & expiry_time); 

注意后兩種的區別,說明以下2種用法是等價的:

1 boost::asio::deadline_timer t(io, boost::posix_time::microsec_clock::universal_time()+boost::posix_time::seconds(5));  
2 boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));  

 

在這個簡單的程序中,我們用定時器演示一個阻塞等待。deadline_timer::wait() 函數調用直到定時器終止(從定時器被創建算起,五秒后終止)才會返回。

一個deadline timer 通常是下面兩種狀態中的一種:"expired(終止)" 或"not expired(不終止)"。如果deadline_timer::wait() 函數被一個已經終止的定時器調用, 它將立即返回。

t.wait();

最后我們打印出 "Hello, world!" 信息以顯示定時器已經終止。 

std::cout << "Hello, world!\n";

代碼:

 1 #include <iostream>  
 2 #include <boost/asio.hpp>  
 3 #include <boost/date_time/posix_time/posix_time.hpp>  
 4   
 5 int main()  
 6 {  
 7   boost::asio::io_service io;  
 8   
 9   boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));  
10   t.wait();  
11   
12   std::cout << "Hello, world!\n";  
13   
14   return 0;  
15 }

2. Timer.2 - 使用異步定時器 

本例使用Asio的異步回調功能在定時器中演示一個異步等待。

使用Asio的異步功能意味着當一個異步操作完成時一個回調函數將被調用。在本程序中我們定義一個名為print 的函數,在異步等待結束后這個函數將被調用。

void print(const boost::system::error_code& /*e*/)
{
std::cout << "Hello, world!\n";
}

int main()
{
boost::asio::io_service io;

boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));

接下來,我們調用 deadline_timer::async_wait() 函數執行一個異步等待去取代Timer.1例中的阻塞等待。當調用這個函數時我們傳入上面定義的print回調句柄。

t.async_wait(print);

最后,我們必須在io_service對象上調用io_service::run()成員函數。

Asio保證回調句柄僅僅能被io_service::run()啟動的當前線程所調用。 因此,如果io_service::run() 函數不執行,用於異步等待完成時的回調函數(在本例中為print函數)將永遠不會被調用。

當仍舊有“工作”可做時,io_service::run() 函數會繼續運行。在本例中,“工作”是定時器的異步等待,因此,直到定時器終止和回調函數執行完成,程序才會返回。

在調用io_service::run()之前確保給 io_service 一些工作去做,這非常重要。 例如,如果我們省略了上面調用的deadline_timer::async_wait() 函數,io_service對象將沒有任何事情去做,因此io_service::run() 將立即返回。

io.run();

和同步方式相比,它主要有兩點不同:
(1) 調用的是非阻塞函數async_wait,它的入參是一個回調函數。
(2) 顯式調用io_service.run()函數驅動異步IO調度。

 

值得提出的是,異步回調函數handler的參數中有一個error,注意這個error很重要,表明這個handler是因為超時被執行還是因為被cancel。

符合2種情況之一,handler被執行:超時或者被cancel(可以通過此error的值進行區分)。
這同時隱含的說明了除非io.stop被調用,否則handler一定會被執行。即便是被cancel。
被cancel有多種方法,直接調用cancel或者調用expires_at,expires_from_now重新設置超時時間。

代碼:

 1 #include <iostream>    
 2 #include <boost/asio.hpp>  
 3 #include <boost/thread.hpp>  
 4 #include <boost/date_time/posix_time/posix_time.hpp>  
 5 using namespace std;    
 6    
 7   
 8 void Print(const boost::system::error_code &ec)  
 9 {  
10     cout<<"Hello World!"<<endl;  
11     cout<<boost::this_thread::get_id()<<endl;  
12 }  
13 int main()  
14 {    
15     cout<<boost::this_thread::get_id()<<endl;  
16     boost::asio::io_service io;  
17     boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));  
18   
19     t.async_wait(Print);  
20     cout<<"to run"<<endl;  
21     io.run();  
22     cout<<"exit"<<endl;  
23     return 0;    
24 }

結果:

 

2f98
to run

(此處等了5s)
Hello World!
2f98  (說明是同一線程)
exit

3. 回調函數綁定參數 

本例使定時器每秒被激活一次。例子將示范如何給你的函數指針傳遞附加參數。

 

使用Asio實現一個重復定時器,你必須在你的回調函數中去改變定時器的終止時間,然后開始一個新的異步等待。顯然這意味着回調函數必須擁有改變定時器對象的權限。為此我們為 print函數增加兩個新參數:

(1) 一個指向定時器對象的指針。 
(2) 一個用於當定時器第6次被激活時我們可以中止程序的計數器。 

void print(const boost::system::error_code& /*e*/,
boost::asio::deadline_timer* t, int* count)
{
如上所示,示例程序使用了一個計數器,當定時器被第6次激活時,用來中止程序。然而,你將看到這里並沒有顯式地要求io_service對象中止。回憶示例2中,當沒有更多“工作”去做時,io_service::run() 函數完成。在計數器達到 5時,定時器並沒有啟動一個新的異步等待。該io_service執行完工作后停止運行。

if (*count < 5)
{
std::cout << *count << "\n";
++(*count);

接着,我們推遲定時器的終止時間。通過在原先的終止時間上增加延時,我們可以確保定時器不會在處理回調函數所需時間內到期。 

t->expires_at(t->expires_at() + boost::posix_time::seconds(1));

接着我們在定時器中啟動一個新的異步等待。我們必須使用boost::bind() 函數給你的回調函數綁定額外的參數,因為deadline_timer::async_wait() 函數只期望得到一個擁用 void(const boost::system::error_code&) 簽名的函數指針(或函數對象)。為你的print函數綁定附加的參數后,它就成為與簽名精確匹配的函數對象。

在本例中,boost::bind()的boost::asio::placeholders::error參數是為了給回調函數傳入一個error對象。當開始異步操作時,如果使用boost::bind(),你必須指定和回調函數的參數列表相匹配的一個參數。在示例4中,如果在回調函數中,這個參數不是必需的,這個占位符會被省略。

t->async_wait(boost::bind(print,
boost::asio::placeholders::error, t, count));
}
}

int main()
{
boost::asio::io_service io;

為了在定時器第4次被激活時終止程序,我們添加一個新的count變量。 

int count = 0;
boost::asio::deadline_timer t(io, boost::posix_time::seconds(1));

在第四步中,當在主函數中的調用deadline_timer::async_wait() 函數時,我們綁定print函數所需要的附加參數。 

t.async_wait(boost::bind(print,
boost::asio::placeholders::error, &t, &count));

io.run();

最后,為了證明count 變量在print 函數句柄中被使用,我們打印出它的值。 

std::cout << "Final count is " << count << "\n";

代碼

 1 #include <iostream>    
 2 #include <boost/asio.hpp>  
 3 #include <boost/thread.hpp>  
 4 #include <boost/date_time/posix_time/posix_time.hpp>  
 5 using namespace std;    
 6    
 7   
 8 void Print(const boost::system::error_code &ec,  
 9             boost::asio::deadline_timer* pt,  
10             int * pcount)  
11 {  
12     if (*pcount < 3)  
13     {  
14         cout<<"count = "<<*pcount<<endl;  
15         cout<<boost::this_thread::get_id()<<endl;  
16         (*pcount) ++;  
17   
18         pt->expires_at(pt->expires_at() + boost::posix_time::seconds(5)) ;  
19   
20         pt->async_wait(boost::bind(Print, boost::asio::placeholders::error, pt, pcount));  
21           
22     }  
23 }  
24 int main()  
25 {    
26     cout<<boost::this_thread::get_id()<<endl;  
27     boost::asio::io_service io;  
28     boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));  
29     int count = 0;  
30     t.async_wait(boost::bind(Print, boost::asio::placeholders::error, &t, &count));  
31     cout<<"to run"<<endl;  
32     io.run();  
33     cout << "Final count is " << count << "\n";  
34     cout<<"exit"<<endl;  
35     return 0;    
36 }

結果:

 

14d0
to run

(等了5s)
count = 0
14d0

(等了5s)
count = 1
14d0

(等了5s)
count = 2
14d0

(等了5s)
Final count is 3
exit

 

 

4. 多線程同步回調 

 

本例示范了使用boost::asio::strand 類來創建多線程程序中的同步回調句柄。 

前幾個例程只是在單線程下使用io_service::run() 函數來避免處理函同步。 如你所見,Asio庫保證回調句柄僅能被當前正在調用 io_service::run(). 函數的線程調用。 因此,在單線程中調用io_service::run() 能確保回調句柄不被並發運行。

下面是Asio在單線程程序中的局限性,尤其是服務器方面,包括: 

(1)操作需要較長時間處理才能完成時弱響應。 
(1)在大規模的多處理機系統中表現不佳。 


如果你發現自己陷入這些局限時,一個可供選擇的方法是創建一個每個線程都調用io_service::run() 的線程池。 不過,因為這允許並發操作,當訪問一個共享、非線程安全的資源時,我們需要一個同步方式。

讓我們從定義一個名為printer的類開始,這與前一個示例中的類很相似。這個類是上一個例子的擴展,這里我們使用兩個並行的定時器。

class CPrinter
{

除了初始化一對boost::asio::deadline_timer 成員變量外,構造函數還初始化一個boost::asio::strand類型strand_ 成員變量。

boost::asio::strand 對象保證:對於通過它來分派執行的眾操作中,只有一個操作執行完成之后才允許進入下一個操作。 這種保證與多少個線程調用io_service::run() 無關。當然,如果不是通過一個boost::asio::strand對象分派, 或者通過其它不同的boost::asio::strand對象分派,這些操作仍舊可能是並發的。

CPrinter(boost::asio::io_service &io)
:m_strand(io)
,m_timer1(io, boost::posix_time::seconds(5))
,m_timer2(io, boost::posix_time::seconds(5))
,m_count(0)
{

 

當開始同步操作時,每一個回調句柄都使用boost::asio::strand對象進行“包裝”。strand::wrap() 函數返回一個新的通過boost::asio::strand對象自動分派的內部句柄。 通過同一boost::asio::strand對象對句柄進行“ 包裝”,我們可以保證操作不會並發執行。

m_timer1.async_wait(m_strand.wrap(boost::bind(&CPrinter::Print1, this) ));
m_timer2.async_wait(m_strand.wrap(boost::bind(&CPrinter::Print2, this) ));
}

~CPrinter()
{
cout<<"m_count = "<<m_count<<endl;
}

在一個多線程程序中,當訪問同一共享資源時,異步操作必須是同步的。在本例中,print1 和print2)函數使用的共享資源std::cout 和count_數據成員。

void Print1()
{
if (m_count < 10)
{
cout<<"Timer1 count = "<<m_count<<endl;
cout<<boost::this_thread::get_id()<<endl;
m_count++;

m_timer1.expires_at(m_timer1.expires_at() + boost::posix_time::seconds(1)) ;

m_timer1.async_wait(m_strand.wrap(boost::bind(&CPrinter::Print1, this) ) );
}
}

void Print2()
{
if (m_count < 10)
{
cout<<"Timer2 count = "<<m_count<<endl;
cout<<boost::this_thread::get_id()<<endl;
m_count++;

m_timer2.expires_at(m_timer2.expires_at() + boost::posix_time::seconds(1)) ;

m_timer2.async_wait(m_strand.wrap(boost::bind(&CPrinter::Print2, this) ) );
}
}
private:
boost::asio::strand m_strand;
boost::asio::deadline_timer m_timer1;
boost::asio::deadline_timer m_timer2;
int m_count;

};

main函數中, io_service::run() 現在被兩個線程調用:主線程和一個附加線程。這一切依賴於boost::thread對象來完成。 

正如它被一個單線程調用一樣,io_service::run()的並發調用會一直持續到無任何“工作”可做。后台線程直到所有異步操作都完成后才會退出。

int main()

cout<<boost::this_thread::get_id()<<endl;
boost::asio::io_service io;
CPrinter cp(io);

cout<<"to run"<<endl;

boost::thread td(boost::bind(&boost::asio::io_service::run, &io));


io.run();

cout<<"exit"<<endl;
return 0; 
}

 

代碼

 
         
 1 #include <boost/asio.hpp>
 2 #include <boost/date_time/posix_time/posix_time.hpp>
 3 #include <boost/thread/thread.hpp>
 4 
 5 boost::asio::io_service Service;
 6 
 7 class Timer
 8 {
 9 private:
10     boost::asio::deadline_timer timer1;
11     boost::asio::deadline_timer timer2;
12     boost::asio::strand m_strand;
13     int count;
14 
15 public:
16     Timer(boost::asio::io_service& ref_service)
17         :timer1(ref_service, boost::posix_time::seconds(3))
18         , timer2(ref_service, boost::posix_time::seconds(3))
19         , m_strand(ref_service)
20         , count(0)
21     {
22 
23         timer1.async_wait(m_strand.wrap(boost::bind(&Timer::Print1, this, boost::asio::placeholders::error)));
24         timer2.async_wait(m_strand.wrap(boost::bind(&Timer::Print2, this, boost::asio::placeholders::error)));
25 
26     }
27     ~Timer()
28     {
29 
30     }
31     void Print1(const boost::system::system_error& error)
32     {
33         if (count < 5)
34         {
35             std::cout << "Pints1's count:" << count<<std::endl;
36             std::cout << boost::this_thread::get_id() <<std:: endl;
37             ++count;
38             timer1.expires_at(timer1.expires_at() + boost::posix_time::seconds(1));
39             timer1.async_wait(m_strand.wrap(boost::bind(&Timer::Print1, this, boost::asio::placeholders::error)));
40         }
41         else
42         {
43             std::cout << "the Error is:" << error.what() << std::endl;
44         }
45     }
46 
47     void Print2(const boost::system::system_error& error)
48     {
49         if (count < 5)
50         {
51             std::cout << "Print2's count :" <<count<< std::endl;
52             std::cout << boost::this_thread::get_id() << std::endl;
53             ++count;
54             timer2.expires_at(timer2.expires_at() + boost::posix_time::seconds(1));
55             timer2.async_wait(m_strand.wrap(boost::bind(&Timer::Print2, this, boost::asio::placeholders::error)));
56         }
57         else
58         {
59             std::cout << "the Error is:" << error.what() << std::endl;
60             boost::asio::io_service::work work(Service);
61         }
62     }
63 
64 };
65 
66 int main(int argc, char** argv)
67 {
68     
69     std::cout << boost::this_thread::get_id() << std::endl;
70     Timer m_timer(Service);
71 
72 
73     std::cout << "異步給主線程運行到io_Service::run之前,知道異步回調函數調完" << std::endl;
74     boost::thread t([&](){ std::cout << "\n進入t線程內部執行" << boost::this_thread::get_id() << std::endl;; Service.run();
75                         std::cout << "t線程內部:異步給主線程運行到io_Service::run之前,知道異步回調函數調完    (驗證)!" << std::endl; });
76     t.detach();
77 
78     std::cout << "異步給主線程運行到io_Service::run之前,知道異步回調函數調完" << std::endl;
79     Service.run();
80     std::cout << "異步給主線程運行到io_Service::run之前,知道異步回調函數調完    (驗證)!" << std::endl;
81     getchar();
82     return 0;
83 }
 
         

運行結果:

 1 2544
 2 異步給主線程運行到io_Service::run之前,知道異步回調函數調完
 3 異步給主線程運行到io_Service::run之前,知道異步回調函數調完
 4 進入t線程內部執行23d4
 5 
 6 Print2's count :0
 7 23d4
 8 Pints1's count:1
 9 2544
10 Pints1's count:2
11 2544
12 Print2's count :3
13 23d4
14 Print2's count :4
15 23d4
16 the Error is:操作成功完成。
17 the Error is:操作成功完成。
18 異步給主線程運行到io_Service::run之前,知道異步回調函數調完    (驗證)!
19 t線程內部:異步給主線程運行到io_Service::run之前,知道異步回調函數調完    (驗證)!

 

說明:

(1)兩個Timer確實是在不同線程中執行,並且只有一個print操作執行完成之后才允許進入另一個print操作

(2)Timer1始終在一個線程中執行,Timer2始終在另一個線程中執行,(但不一定就是Timer1在主線程執行,這個分配時隨機的)

 

注意:

deadline_timer和socket一樣,都用io_service作為構造函數的參數。在其上進行異步操作,都將導致和io_service所包含的iocp相關聯。這同樣意味着在析構 io_service之前,必須析構關聯在這個io_service上的deadline_timer

 

引申:

關於boost::asio::strand,有三個函數:post, dispatch, wrap

post: 不管什么情況都會把任務丟到隊列中,然后立即返回。如:m_strand.post(boost::bind(print, 2));
dispatch: 如果跟run()在一個線程,那么任務會直接在dispatch內部調用,執行結束后返回。不在一個線程跟post一樣。如:m_strand.dispatch(boost::bind(print, 1));

wrap:返回一個新的通過boost::asio::strand對象自動分派的內部句柄

注意事項:

異步等待時,程序會先執行主線程需要執行的操作,直到io_service::run之前的語句;當回調函數都執行完成之后,才繼續執行io_service::run之后的操作,

有時你如果沒給io_service::run做點事,run會立馬返回,導致程序無響應,解決辦法是添加boost::asio::io_service::work work(io_Service), work對象不析構,run是不會立即返回的!

work轉載來自:http://blog.csdn.net/huang_xw/article/details/8471057

 

其中異步調用時,用boost::asio::strand,strand.wrap(boost::bind(....)),為了在函數共享數據時,使函數一縷一縷的執行,strand會內部打包函數,返回自己隨機分配函數句炳!

 

還有,異步等待的時間要夠回調函數的執行時間,否則會直接調用異步等待,所以你可以加入延時函數,deadline_timer::expires_at(.expires.at()+boost::posix_time::senconds(5));保證回調函數的執行時間;


本文轉載自:http://blog.csdn.net/yockie/article/details/40386145

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM