1. 程式人生 > >boost.Asio Example定時器的思考---結果阻礙了我們對本質的思考

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中過程操作順序的,所以兩個非同步事件共享資料我們還是要加鎖或者其他方式控制的,雖然有的實現方式不是用執行緒,但是還是大同小異的,為了保障程式的正確執行我們還是要不依賴於實現,而是依賴於邏輯!