boost.Asio Example定時器的思考---結果阻礙了我們對本質的思考
boost.Asio官網給的教程很多關於定時器的例子,現在我就來研究下這幾個例子
Example 1:
//
// timer.cpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <iostream>
#include <asio.hpp>
int main()
{
asio::io_context io;
asio::steady_timer t(io, asio::chrono::seconds(5));
t.wait();
std::cout << "Hello, world!" << std::endl;
return 0;
}
第一個例子很簡單,主要向我們介紹同步的概念,先是設定了一個定時器然後等待5秒啥事也不幹,然後輸出Hello,World,第一個例子彷彿是先吹再黑,你看把單執行緒下不用非同步,搞半天才輸出個Hello,World,比如網路中你給每個人連線上的人傳送Hello,World,而處理第一個連線後你就讓整個程式等5秒然後傳送Hello,World,然後處理第二個連線時,又是這樣,這樣必然浪費資源
怎麼辦呢?接下來官網給了第二個例子
//
// timer.cpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <iostream>
#include <asio.hpp>
void print(const asio::error_code& /*e*/)
{
std::cout << "Hello, world!" << std::endl;
}
int main()
{
asio::io_context io;
asio::steady_timer t(io, asio::chrono::seconds(5));
t.async_wait(&print);
io.run();
return 0;
}
這裡介紹了非同步的概念雖然結果和第一個例子結果一致,但是思想卻不同,
第一種呢是你中午睡覺(你這人太累,睡覺就像死了一樣,不做夢),睡覺之後然後去幹活
第二種是你飽暖思淫慾,睡覺還要做夢.然後去幹活
第一種可以看作是對整個過程的阻塞,而第二種是對過程中的一部分阻塞,但不影響其他過程的順序執行(除非其他過程部分等待非同步結束)
下面來討論第三個例子
//
// timer.cpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <iostream>
#include <asio.hpp>
#include <boost/bind.hpp>
void print(const asio::error_code& /*e*/,
asio::steady_timer* t, int* count)
{
if (*count < 5)
{
std::cout << *count << std::endl;
++(*count);
t->expires_at(t->expiry() + asio::chrono::seconds(1));
t->async_wait(boost::bind(print,
asio::placeholders::error, t, count));
}
}
int main()
{
asio::io_context io;
int count = 0;
asio::steady_timer t(io, asio::chrono::seconds(1));
t.async_wait(boost::bind(print,
asio::placeholders::error, &t, &count));
io.run();
std::cout << "Final count is " << count << std::endl;
return 0;
}
這只是個每秒輸出一個數的例子,非常簡單,只是為了熟悉一下API
第四個例子也是這樣
下面重點是第五個例子,為了直觀的輸出,我對例子輸出作了修改加了點提示
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
using namespace boost;
class printer {
public:
printer(asio::io_context& io) :strand_(io),
timer1_(io,asio::chrono::seconds(1)),timer2_(io,asio::chrono::seconds(1)),count_(0){
timer1_.async_wait(asio::bind_executor(strand_,boost::bind(&printer::print1,this)));
timer2_.async_wait(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() + asio::chrono::seconds(1));
timer1_.async_wait(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() + asio::chrono::seconds(1));
timer2_.async_wait(asio::bind_executor(strand_,boost::bind(&printer::print2,this)));
}
}
private:
asio::steady_timer timer1_;
asio::steady_timer timer2_;
int count_;
asio::io_context::strand strand_;
};
int main() {
asio::io_context io;
printer p(io);
asio::detail::thread t(boost::bind(&asio::io_context::run,&io));
io.run();
t.join();
return 0;
}
這裡用了執行緒,和asio的asio::io_context::strand,這個鬼東西是幹嘛的,我們觀察這個程式的輸出
貌似我們不用asio::io_context::strand這東西也能做到
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
using namespace boost;
class printer {
public:
printer(asio::io_context& io):timer1_(io,asio::chrono::seconds(1)),timer2_(io,asio::chrono::seconds(1)),count_(0){
timer1_.async_wait(bind(&printer::print1,this));
timer2_.async_wait(bind(&printer::print2,this));
}
private:
void print1() {
if (count_ < 10) {
std::cout << "TimerId: timer 1" << " " << "count: " << count_ << std::endl;
++count_;
timer1_.expires_at(timer1_.expiry() + asio::chrono::seconds(1));
timer1_.async_wait(bind(&printer::print1,this));
}
}
void print2() {
if (count_ < 10) {
std::cout << "TimerId: timer 2" << " " << "count: " << count_ << std::endl;
++count_;
timer2_.expires_at(timer2_.expiry() + asio::chrono::seconds(1));
timer2_.async_wait(bind(&printer::print2,this));
}
}
int count_;
asio::steady_timer timer1_;
asio::steady_timer timer2_;
};
int main() {
asio::io_context io;
printer p(io);
io.run();
return 0;
}
執行發現和它也沒差異
那麼引入asio::io_context::strand的目的是幹嘛的呢
英語上是擱淺的意思,作用就是在所有註冊的回撥函式都不能同時執行,就是封裝所有回撥函式是原子性的,那我沒用這個東西卻保證了相同結果的有什麼侷限性呢,我認為侷限性有兩點,
第一:沒使用strand時間可能會長一點(當然這不是使用strand)的主要原因,據說strand內部實現並未用鎖,使用strand使得顆粒度減小,通過隔離過程來保障資料安全(如果內部用了鎖,那還不如用來鎖資料)
第二點:非同步的實現,這是最重要的,我寫的程式碼其實是有邏輯錯誤的,Timer 1和Timer 2都使用了同一個資料count_,不僅讀取還進行了修改,我們註冊了兩個非同步事件,然而我們卻沒有保障事件A在改count_時,事件B不在讀取,有人可能說這是單執行緒不可能出現這種情況,但是我們用的別人寫函式,而且系統還有排程問題,非同步只是一種表面的結果,我們完全可以使用執行緒來模擬非同步,A事件用執行緒A,B事件用執行緒B就會出現問題了,因為非同步A和非同步B是沒有隔絕的,非同步的概念是不能確保A中過程和B中過程操作順序的,所以兩個非同步事件共享資料我們還是要加鎖或者其他方式控制的,雖然有的實現方式不是用執行緒,但是還是大同小異的,為了保障程式的正確執行我們還是要不依賴於實現,而是依賴於邏輯!