本教程使用了簡單的異步計時器演示了asio的基本使用。
同步使用定時器
如何實現阻塞等待定時器。首先引入頭文件
#include <iostream>
#include <boost/asio.hpp>
"asio.hpp"可以簡單地幫我們將所需的頭文件引入。
使用asio的所有程序都需要至少一個I/O execution context,像io_context
或者thread_pool
對象。通過I/O execution context我們來訪問I/O功能。
在主函數中聲明一個io_context
的對象
int main() {
boost::asio::io_context io;
}
接下來再聲明一個boost::asio::steady_timer
類型的對象。asio中提供I/O功能的核心類(例如本例中的定時器)總是將io_context
作為第一個參數。在本例的定時器中,將過期時間設置為第二個參數。
boost::asio::steady_timer t(io,boost::asio::chrono::seconds(5));
在這個簡單的例子中,我們對定時器執行阻塞等待。也就是說,對 steady_timer::wait()
的調用將不會返回,直到定時器到期,即創建后 5 秒。
定時器始終處於以下兩種狀態之一:“過期”或“未過期”。 如果對過期的定時器調用了 steady_timer::wait()
函數,它將立即返回。
t.wait();
最后,在定時器到期后,我們習慣性地打印“Hello,World!” 。
完整代碼如下:
#include <iostream>
#include <boost/asio.hpp>
int main()
{
boost::asio::io_context io;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
t.wait();
std::cout << "Hello, world!" << std::endl;
return 0;
}
異步使用定時器
實現一個異步等待的定時器。
使用 asio 的異步功能意味着擁有一個回調函數,該函數將在異步操作完成時被調用。 在這個程序中,我們定義了一個名為 print 的函數,在異步等待完成時調用它。
void print(const boost::system::error_code& /*e*/)
{
std::cout << "Hello, world!" << std::endl;
}
int main()
{
boost::asio::io_context io;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
接下來與同步方式不同的是,我們讓定時器異步等待,並將上面定義的回調函數傳遞過去
t.async_wait(&print);
最后,我們必須要調用io_context::run()
成員函數。
因為asio保證異步回調函數只會在調用io_context::run()
函數的線程中執行。所以,不調用該函數,就永遠無法取得異步函數執行結果並進行回調。
只要還有異步操作沒有完成,那么io_context::run()
函數就不會返回。
完整代碼如下:
#include <iostream>
#include <boost/asio.hpp>
void print(const boost::system::error_code& /*e*/)
{
std::cout << "Hello, world!" << std::endl;
}
int main()
{
boost::asio::io_context io;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
t.async_wait(&print);
io.run();
return 0;
}
為Handler綁定參數
本節演示如何為Handler附加參數。
我們要實現每秒定時打印一次信息,最多打印5次。
引入頭文件
#include <iostrea>
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
要實現每秒打印,那么我們就要在回調函數中更改定時器的到期時間,然后開始新的異步等待。這就意味着,在回調函數中我們要能夠訪問定時器對象和當前的次數,所以需要添加兩個參數:
- 指向定時器對象的指針
- 當前計數值
void print(const boost::system::error_code& /*e*/,
boost::asio::steady_timer* t, int* count)
{
if (*count < 5)
{
std::cout << *count << std::endl;
++(*count);
接下來,我們將定時器的到期時間從前一個到期時間向前移動一秒。 通過舊的時間計算新的到期時間,我們可以確保計時器不會由於處理處理程序的任何延遲而偏離整秒標記。
t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));
steady_timer::async_wait()
函數需要一個簽名為 void(const boost::system::error_code&)
的處理函數(或函數對象),我們使用boost::bind()
函數來講print函數轉為與要求函數簽名一致的函數對象。
在此示例中,boost::bind()
的 boost::asio::placeholders::error
參數是傳遞給處理程序的錯誤對象的命名占位符。 啟動異步操作時,如果使用 boost::bind(),則必須僅指定與處理程序的參數列表匹配的參數。
t->async_wait(boost::bind(print,
boost::asio::placeholders::error, t, count));
}
}
完整代碼如下:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
void print(const boost::system::error_code& /*e*/,
boost::asio::steady_timer* t, int* count)
{
if (*count < 5)
{
std::cout << *count << std::endl;
++(*count);
//修改過期時間
t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));
//啟動異步等待
t->async_wait(boost::bind(print,
boost::asio::placeholders::error, t, count));
}
}
int main()
{
boost::asio::io_context io;
int count = 0;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(1));
t.async_wait(boost::bind(print,
boost::asio::placeholders::error, &t, &count));
io.run();
std::cout << "Final count is " << count << std::endl;
return 0;
}
使用成員函數作為Handler
本節中,我們使用類的一個成員函數作為回調函數,實現與上一節相同的功能。
不同於上一節使用print函數作為回調函數,這里我們使用print類作為回調處理器。
我們將定時器與計數都封裝在內部,在構造函數中就啟動異步操作
class Printer
{
public:
Printer(boost::asio::io_context* ioc) : timer_(ioc)
{
timer_.async_wait(boost::bind(&Printer::print,this));
}
~Printer()
{
std::cout << "Final count is " << count_ << std::endl;
}
void print()
{
if(count_ < 5)
{
std::cout << count_ << std::endl;
++count_;
timer_.expires_at(timer_.expiry() + boost::asio::chrono::seconds(1));
timer_.async_wait(boost::bind(&Printer::print,this));
}
}
private:
boost::asio::steady_timer timer_;
int count_;
};
完整代碼如下:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
class printer
{
public:
printer(boost::asio::io_context& io)
: timer_(io, boost::asio::chrono::seconds(1)),
count_(0)
{
timer_.async_wait(boost::bind(&printer::print, this));
}
~printer()
{
std::cout << "Final count is " << count_ << std::endl;
}
void print()
{
if (count_ < 5)
{
std::cout << count_ << std::endl;
++count_;
timer_.expires_at(timer_.expiry() + boost::asio::chrono::seconds(1));
timer_.async_wait(boost::bind(&printer::print, this));
}
}
private:
boost::asio::steady_timer timer_;
int count_;
};
int main()
{
boost::asio::io_context io;
printer p(io);
io.run();
return 0;
}
多線程程序中的同步處理程序
我們知道調用了io_context::run()
的函數會執行回調函數。前面幾節中,我們都只在一個線程中調用了該函數,因此不會並發執行。
在使用 asio 開發應用程序時,單線程方法通常是最好的方法。 缺點是它對程序(尤其是服務器)的限制,包括:
- 當處理程序可能需要很長時間才能完成時,響應能力差。
- 無法在多處理器系統上擴展。
如果您發現自己遇到了這些限制,另一種方法是讓線程池調用 io_context::run()
。然而,由於這允許處理程序並發執行,當處理程序可能訪問共享的、線程不安全的資源時,我們需要一種同步方法。
在上一節的基礎上,我們使用兩個定時器,總共輸出10條信息后結束。
我們同樣定義一個Printer類,將其擴展為並行運行兩個定時器。
除了初始化一對 boost::asio::steady_timer
成員之外,構造函數還初始化了 strand_
成員,它是 boost::asio::strand<boost::asio::io_context::executor_type>
類型的對象。
strand類模板是一個執行器適配器,它保證通過它分派的處理程序能夠在啟動下一個處理程序之前完成一個正在執行的處理程序。無論調用io context::run()的線程數量如何,這都是可以保證的。當然,處理程序仍然可以與其他處理程序並發執行,這些處理程序不是通過一個鏈分派的,或者是通過一個不同的鏈對象分派的。
class printer
{
printer(boost::asio::io_context& io)
: strand_(boost::asio::make_strand(io)),
timer1_(io, boost::asio::chrono::seconds(1)),
timer2_(io, boost::asio::chrono::seconds(1)),
count_(0)
{
};
啟動異步操作時,每個回調處理程序都“綁定”到一個 boost::asio::strand<boost::asio::io_context::executor_type>
對象。 boost::asio::bind_executor()
函數返回一個新的處理程序,該處理程序通過strand對象自動分派其包含的處理程序。 通過將處理程序綁定到同一條strand,我們確保它們不能同時執行。
timer1_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print1, this)));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print2, this)));
}
~printer()
{
std::cout << "Final count is " << count_ << std::endl;
}
在多線程程序中,如果異步操作的處理程序訪問共享資源,則它們應該同步。 在本教程中,處理程序(print1 和 print2)使用的共享資源是 std::cout
和count_
數據成員。
void print1()
{
if (count_ < 10)
{
std::cout << "Timer 1: " << count_ << std::endl;
++count_;
timer1_.expires_at(timer1_.expiry() + boost::asio::chrono::seconds(1));
timer1_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print1, this)));
}
}
void print2()
{
if (count_ < 10)
{
std::cout << "Timer 2: " << count_ << std::endl;
++count_;
timer2_.expires_at(timer2_.expiry() + boost::asio::chrono::seconds(1));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print2, this)));
}
}
private:
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
boost::asio::steady_timer timer1_;
boost::asio::steady_timer timer2_;
int count_;
};
現在,主函數會導致從兩個線程調用 io_context::run()
:主線程和一個附加線程。 這是使用boost::thread
對象完成的。
int main()
{
boost::asio::io_context io;
printer p(io);
boost::thread t(boost::bind(&boost::asio::io_context::run, &io));
io.run();
t.join();
return 0;
}
完整代碼如下所示:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread/thread.hpp>
#include <boost/bind/bind.hpp>
class printer
{
public:
printer(boost::asio::io_context& io)
: strand_(boost::asio::make_strand(io)),
timer1_(io, boost::asio::chrono::seconds(1)),
timer2_(io, boost::asio::chrono::seconds(1)),
count_(0)
{
timer1_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print1, this)));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print2, this)));
}
~printer()
{
std::cout << "Final count is " << count_ << std::endl;
}
void print1()
{
if (count_ < 10)
{
std::cout << "Timer 1: " << count_ << std::endl;
++count_;
timer1_.expires_at(timer1_.expiry() + boost::asio::chrono::seconds(1));
timer1_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print1, this)));
}
}
void print2()
{
if (count_ < 10)
{
std::cout << "Timer 2: " << count_ << std::endl;
++count_;
timer2_.expires_at(timer2_.expiry() + boost::asio::chrono::seconds(1));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print2, this)));
}
}
private:
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
boost::asio::steady_timer timer1_;
boost::asio::steady_timer timer2_;
int count_;
};
int main()
{
boost::asio::io_context io;
printer p(io);
boost::thread t(boost::bind(&boost::asio::io_context::run, &io));
io.run();
t.join();
return 0;
}