跳到正文

Wiki

定時器與非同步 IO

約 9 分鐘閱讀

本文由簡體中文內容確定性轉換,並受版本化術語表保護。

本節是整套 Boost.Asio 教程最重要的一節。
序列埠、TCP、UDP 的非同步模型都和定時器類似:先註冊非同步操作,然後由 io_context.run() 驅動回撥執行。

本節所有非同步回撥都使用 std::bind,暫時不使用 lambda。


1.示例 1:普通 main() 裡寫同步定時器

1.1.程式目標

先寫最簡單的阻塞式定時器:程式啟動後立刻列印一句話,然後阻塞等待 2 秒,再列印結束。

1.2.完整程式碼

#include <boost/asio.hpp>
#include <chrono>
#include <iostream>

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
    boost::asio::io_context io;

    // 创建定时器,并设置到期时间。
    boost::asio::steady_timer timer(io, std::chrono::seconds(2));

    std::cout << "程序开始:准备等待 2 秒" << std::endl;

    // 同步等待会阻塞当前线程,时间没到之前不会继续往下执行。
    timer.wait();

    std::cout << "2 秒到了:timer.wait() 返回" << std::endl;

    return 0;
}

執行結果:見下方“執行輸出與時間順序”;如果示例涉及定時器、執行緒、網路或外部裝置,具體時間和順序可能會隨環境略有變化。

1.3.編譯執行

g++ demo1_sync_timer.cpp -o demo1_sync_timer -std=c++17 -lboost_system -pthread
./demo1_sync_timer

1.4.執行輸出與時間順序

程序开始:准备等待 2 秒

等待約 2 秒後:

2 秒到了:timer.wait() 返回

完整輸出類似:

程序开始:准备等待 2 秒
2 秒到了:timer.wait() 返回

1.5.本示例需要注意的點

這個程式沒有體現“非同步”,因為 timer.wait() 會直接阻塞當前執行緒。

也就是說,程式卡在這裡:

timer.wait();

2 秒沒有到之前,下一行不會執行。

1.6.關鍵函式說明

1.6.1.boost::asio::io_context io;

作用:建立一個 Asio 事件迴圈物件。

在同步 wait() 示例中,它看起來沒什麼存在感;但到了非同步版本,所有非同步事件都要靠它驅動。

1.6.2.boost::asio::steady_timer timer(io, std::chrono::seconds(2));

作用:建立一個基於穩定時鐘的定時器,設定 2 秒後到期。

引數:

io

表示這個 timer 歸哪個 io_context 管。

std::chrono::seconds(2)

表示相對當前時間 2 秒後到期。

1.6.3.timer.wait();

作用:阻塞等待 timer 到期。

返回值:void

特點:簡單,但是會阻塞當前執行緒。機器人程式裡如果在主控制迴圈裡這麼寫,很容易卡住整個程式。


2.示例 2:普通 main() 裡寫非同步定時器

2.1.程式目標

把示例 1 改成非同步寫法:

  1. 註冊一個 2 秒定時器;
  2. async_wait() 不阻塞;
  3. io.run() 開始阻塞;
  4. 2 秒後回撥函式被執行;
  5. 沒有任務後 io.run() 返回。

2.2.完整程式碼

#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <chrono>
#include <functional>
#include <iostream>

void on_timer(const boost::system::error_code& ec)
{
    if (ec)
    {
        std::cout << "定时器被取消,错误信息:" << ec.message() << std::endl;
        return;
    }

    std::cout << "回调函数 on_timer:2 秒到了" << std::endl;
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
    boost::asio::io_context io;

    // 创建定时器,并设置到期时间。
    boost::asio::steady_timer timer(io, std::chrono::seconds(2));

    std::cout << "main:注册 async_wait" << std::endl;

    // 注册异步等待:这一行不会阻塞,回调会在定时器到期后执行。
    timer.async_wait(std::bind(on_timer, std::placeholders::_1));

    std::cout << "main:async_wait 已经返回,但回调还没执行" << std::endl;
    std::cout << "main:准备调用 io.run()" << std::endl;

    io.run();

    std::cout << "main:io.run() 返回,程序结束" << std::endl;

    return 0;
}

執行結果:見下方“執行輸出與時間順序”;如果示例涉及定時器、執行緒、網路或外部裝置,具體時間和順序可能會隨環境略有變化。

2.3.編譯執行

g++ demo2_async_timer.cpp -o demo2_async_timer -std=c++17 -lboost_system -pthread
./demo2_async_timer

2.4.執行輸出與時間順序

程式立刻輸出:

main:注册 async_wait
main:async_wait 已经返回,但回调还没执行
main:准备调用 io.run()

然後程式會卡在:

io.run();

等待約 2 秒後輸出:

回调函数 on_timer:2 秒到了
main:io.run() 返回,程序结束

完整輸出類似:

main:注册 async_wait
main:async_wait 已经返回,但回调还没执行
main:准备调用 io.run()
回调函数 on_timer:2 秒到了
main:io.run() 返回,程序结束

2.5.本示例需要注意的點

async_wait() 不會等待 2 秒。它只是把“2 秒後執行回撥”這件事註冊到 io_context 裡。

真正等待的是:

io.run();

所以不要把 async_wait() 理解成“非同步版本的 wait”。更準確地說:

async_wait() = 注册任务
io.run() = 执行事件循环
on_timer() = 任务完成后的回调

2.6.std::bind 說明

這句程式碼:

timer.async_wait(std::bind(on_timer, std::placeholders::_1));

意思是:

当 timer 到期后,Boost.Asio 会传入一个 error_code。
这个 error_code 会填到 std::placeholders::_1 的位置。
最后调用 on_timer(ec)。

on_timer 的函式簽名必須能接收這個引數:

void on_timer(const boost::system::error_code& ec)

2.7.關鍵函式說明

2.7.1.timer.async_wait(handler)

作用:註冊一個非同步等待操作。

引數:一個回撥函式物件。

返回值:void

注意:它不會阻塞等待定時器到期。

2.7.2.io.run()

作用:啟動事件迴圈,處理非同步任務和回撥函式。

返回值:執行過的 handler 數量,型別通常可以看成 std::size_t

本例中,io.run() 執行了 1 個回撥,所以返回值是 1。只是我們沒有列印返回值。


3.示例 3:普通 main() 裡寫兩個非同步定時器

3.1.程式目標

一個 timer 很難看出非同步排程的感覺,所以這次寫兩個 timer:

  1. timer1 1 秒後到期;
  2. timer2 3 秒後到期;
  3. 兩個 timer 都在同一個 io_context 裡;
  4. io.run() 會等兩個任務都完成後才返回。

3.2.完整程式碼

#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <chrono>
#include <functional>
#include <iostream>
#include <string>

void on_timer(const boost::system::error_code& ec, const std::string& name)
{
    if (ec)
    {
        std::cout << name << " 被取消:" << ec.message() << std::endl;
        return;
    }

    std::cout << name << " 到期,执行回调" << std::endl;
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
    boost::asio::io_context io;

    // 创建定时器,并设置到期时间。
    boost::asio::steady_timer timer1(io, std::chrono::seconds(1));
    boost::asio::steady_timer timer2(io, std::chrono::seconds(3));

    std::cout << "main:注册 timer1,1 秒后到期" << std::endl;
    // 注册异步等待:这一行不会阻塞,回调会在定时器到期后执行。
    timer1.async_wait(std::bind(on_timer,
                                std::placeholders::_1,
                                std::string("timer1")));

    std::cout << "main:注册 timer2,3 秒后到期" << std::endl;
    timer2.async_wait(std::bind(on_timer,
                                std::placeholders::_1,
                                std::string("timer2")));

    std::cout << "main:调用 io.run()" << std::endl;

    std::size_t count = io.run();

    std::cout << "main:io.run() 返回,一共执行了 " << count << " 个回调" << std::endl;

    return 0;
}

執行結果:見下方“執行輸出與時間順序”;如果示例涉及定時器、執行緒、網路或外部裝置,具體時間和順序可能會隨環境略有變化。

3.3.編譯執行

g++ demo3_two_timers.cpp -o demo3_two_timers -std=c++17 -lboost_system -pthread
./demo3_two_timers

3.4.執行輸出與時間順序

程式立刻輸出:

main:注册 timer1,1 秒后到期
main:注册 timer2,3 秒后到期
main:调用 io.run()

約 1 秒後輸出:

timer1 到期,执行回调

再過約 2 秒,也就是程式啟動後約 3 秒,輸出:

timer2 到期,执行回调
main:io.run() 返回,一共执行了 2 个回调

完整輸出類似:

main:注册 timer1,1 秒后到期
main:注册 timer2,3 秒后到期
main:调用 io.run()
timer1 到期,执行回调
timer2 到期,执行回调
main:io.run() 返回,一共执行了 2 个回调

3.5.本示例需要注意的點

兩個 timer 不是兩個執行緒。

本例只有一個執行緒,也就是主執行緒。timer1timer2 的回撥都在呼叫 io.run() 的這個主執行緒裡執行。

真正的執行順序是:

主线程进入 io.run()
等待事件
timer1 先到期 -> 调用 timer1 回调
继续等待事件
timer2 后到期 -> 调用 timer2 回调
没有任务了 -> io.run() 返回

3.6.std::bind 裡的固定引數

這句:

std::bind(on_timer, std::placeholders::_1, std::string("timer1"))

等價於提前準備了一個函式物件:

未来 Asio 传入 ec 时,调用 on_timer(ec, "timer1")。

_1 是未來由 Asio 傳入的錯誤碼。

"timer1" 是現在就固定好的引數。


4.示例 4:普通 main() 裡寫重複定時器

4.1.程式目標

實現一個每 1 秒列印一次的定時器,列印 5 次後結束。

這個例子很像機器人裡的週期任務,例如:

每 20ms 发送一次速度命令
每 100ms 检查一次下位机心跳
每 1s 打印一次通信状态

4.2.完整程式碼

#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <chrono>
#include <functional>
#include <iostream>

void repeat_timer(const boost::system::error_code& ec,
                  boost::asio::steady_timer* timer,
                  int* count)
{
    if (ec)
    {
        std::cout << "repeat_timer 被取消:" << ec.message() << std::endl;
        return;
    }

    ++(*count);
    std::cout << "第 " << *count << " 次定时器回调" << std::endl;

    if (*count < 5)
    {
        timer->expires_after(std::chrono::seconds(1));
        timer->async_wait(std::bind(repeat_timer,
                                    std::placeholders::_1,
                                    timer,
                                    count));
    }
    else
    {
        std::cout << "已经执行 5 次,不再重新注册定时器" << std::endl;
    }
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
    boost::asio::io_context io;
    // 创建定时器,并设置到期时间。
    boost::asio::steady_timer timer(io, std::chrono::seconds(1));
    int count = 0;

    std::cout << "main:注册第 1 次定时器" << std::endl;

    // 注册异步等待:这一行不会阻塞,回调会在定时器到期后执行。
    timer.async_wait(std::bind(repeat_timer,
                               std::placeholders::_1,
                               &timer,
                               &count));

    io.run();

    std::cout << "main:io.run() 返回,程序结束" << std::endl;

    return 0;
}

執行結果:見下方“執行輸出與時間順序”;如果示例涉及定時器、執行緒、網路或外部裝置,具體時間和順序可能會隨環境略有變化。

4.3.編譯執行

g++ demo4_repeat_timer.cpp -o demo4_repeat_timer -std=c++17 -lboost_system -pthread
./demo4_repeat_timer

4.4.執行輸出與時間順序

程式立刻輸出:

main:注册第 1 次定时器

約 1 秒後:

第 1 次定时器回调

之後每隔約 1 秒輸出一次:

第 2 次定时器回调
第 3 次定时器回调
第 4 次定时器回调
第 5 次定时器回调
已经执行 5 次,不再重新注册定时器
main:io.run() 返回,程序结束

完整輸出類似:

main:注册第 1 次定时器
第 1 次定时器回调
第 2 次定时器回调
第 3 次定时器回调
第 4 次定时器回调
第 5 次定时器回调
已经执行 5 次,不再重新注册定时器
main:io.run() 返回,程序结束

4.5.本示例需要注意的點

重複定時器不是 while 迴圈裡 sleep

非同步寫法是:

第一次 async_wait
第 1 次回调里重新设置 expires_after
第 1 次回调里再次 async_wait
第 2 次回调里再次注册
……

也就是“每次回撥結束前,決定要不要註冊下一次”。

4.6.指標生命週期說明

本例裡傳了:

&timer
&count

這是為了讓 repeat_timer() 能修改同一個 timer 和 count。

這個寫法在本例中是安全的,因為:

timer 和 count 都是 main() 里的局部变量
main() 会卡在 io.run()
io.run() 返回前,timer 和 count 都不会析构

但是工程裡更推薦把它封裝成類,見下一個示例。


5.示例 5:類裡寫單個非同步定時器

5.1.程式目標

把示例 2 改成類封裝版本。

類封裝是機器人專案裡更常見的寫法,例如:

SerialDriver 类
TcpClient 类
UdpReceiver 类
RobotController 类

5.2.完整程式碼

#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <chrono>
#include <functional>
#include <iostream>

class Printer
{
public:
    explicit Printer(boost::asio::io_context& io)
        : timer_(io, std::chrono::seconds(2))
    {
        std::cout << "Printer 构造:注册 2 秒定时器" << std::endl;

        // 注册异步等待:这一行不会阻塞,回调会在定时器到期后执行。
        timer_.async_wait(std::bind(&Printer::on_timer,
                                    this,
                                    std::placeholders::_1));
    }

private:
    void on_timer(const boost::system::error_code& ec)
    {
        if (ec)
        {
            std::cout << "Printer::on_timer 被取消:" << ec.message() << std::endl;
            return;
        }

        std::cout << "Printer::on_timer:2 秒到了" << std::endl;
    }

private:
    // 创建定时器,并设置到期时间。
    boost::asio::steady_timer timer_;
};

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
    boost::asio::io_context io;

    Printer printer(io);

    std::cout << "main:准备调用 io.run()" << std::endl;

    io.run();

    std::cout << "main:io.run() 返回" << std::endl;

    return 0;
}

執行結果:見下方“執行輸出與時間順序”;如果示例涉及定時器、執行緒、網路或外部裝置,具體時間和順序可能會隨環境略有變化。

5.3.編譯執行

g++ demo5_class_timer.cpp -o demo5_class_timer -std=c++17 -lboost_system -pthread
./demo5_class_timer

5.4.執行輸出與時間順序

程式立刻輸出:

Printer 构造:注册 2 秒定时器
main:准备调用 io.run()

約 2 秒後輸出:

Printer::on_timer:2 秒到了
main:io.run() 返回

完整輸出類似:

Printer 构造:注册 2 秒定时器
main:准备调用 io.run()
Printer::on_timer:2 秒到了
main:io.run() 返回

5.5.建構函式初始化列表說明

這段:

explicit Printer(boost::asio::io_context& io)
    : timer_(io, std::chrono::seconds(2))
{
}

冒號後面叫“建構函式初始化列表”。

它的作用是:在進入建構函式大括號之前,直接構造成員變數 timer_

為什麼這裡必須這麼寫?因為 boost::asio::steady_timer 沒有一個適合你先預設構造、再賦值的簡單寫法。它需要在構造時就知道自己屬於哪個 io_context

5.6.成員函式繫結說明

這句:

timer_.async_wait(std::bind(&Printer::on_timer,
                            this,
                            std::placeholders::_1));

含義是:

定时器到期后,调用 this->on_timer(ec)。

為什麼要寫 &Printer::on_timer

因為 on_timer 是成員函式,不是普通全域性函式。成員函式必須依賴某個物件才能呼叫。

為什麼要傳 this

因為 this 表示當前這個 Printer 物件。

5.7.易錯點

不要寫成:

std::bind(on_timer, std::placeholders::_1)

因為 on_timer 是成員函式,不是普通函式。

也不要漏掉 this

std::bind(&Printer::on_timer, std::placeholders::_1)

這樣不知道要呼叫哪個物件的 on_timer()


6.示例 6:類裡寫兩個定時器

6.1.程式目標

類裡同時維護兩個 timer:

  1. timer1_ 1 秒後執行;
  2. timer2_ 3 秒後執行;
  3. 用這個例子看清楚“兩個非同步任務不等於兩個執行緒”。

6.2.完整程式碼

#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <chrono>
#include <functional>
#include <iostream>

class TwoTimers
{
public:
    explicit TwoTimers(boost::asio::io_context& io)
        : timer1_(io, std::chrono::seconds(1)),
          timer2_(io, std::chrono::seconds(3))
    {
        std::cout << "TwoTimers 构造:注册 timer1 和 timer2" << std::endl;

        // 注册异步等待:这一行不会阻塞,回调会在定时器到期后执行。
        timer1_.async_wait(std::bind(&TwoTimers::on_timer1,
                                     this,
                                     std::placeholders::_1));

        timer2_.async_wait(std::bind(&TwoTimers::on_timer2,
                                     this,
                                     std::placeholders::_1));
    }

private:
    void on_timer1(const boost::system::error_code& ec)
    {
        if (ec)
        {
            std::cout << "timer1 取消:" << ec.message() << std::endl;
            return;
        }

        std::cout << "timer1:1 秒到了" << std::endl;
    }

    void on_timer2(const boost::system::error_code& ec)
    {
        if (ec)
        {
            std::cout << "timer2 取消:" << ec.message() << std::endl;
            return;
        }

        std::cout << "timer2:3 秒到了" << std::endl;
    }

private:
    // 创建定时器,并设置到期时间。
    boost::asio::steady_timer timer1_;
    boost::asio::steady_timer timer2_;
};

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
    boost::asio::io_context io;
    TwoTimers app(io);

    std::cout << "main:调用 io.run()" << std::endl;

    io.run();

    std::cout << "main:两个定时器都执行完,io.run() 返回" << std::endl;

    return 0;
}

執行結果:見下方“執行輸出與時間順序”;如果示例涉及定時器、執行緒、網路或外部裝置,具體時間和順序可能會隨環境略有變化。

6.3.編譯執行

g++ demo6_class_two_timers.cpp -o demo6_class_two_timers -std=c++17 -lboost_system -pthread
./demo6_class_two_timers

6.4.執行輸出與時間順序

程式立刻輸出:

TwoTimers 构造:注册 timer1 和 timer2
main:调用 io.run()

約 1 秒後:

timer1:1 秒到了

約 3 秒後:

timer2:3 秒到了
main:两个定时器都执行完,io.run() 返回

完整輸出類似:

TwoTimers 构造:注册 timer1 和 timer2
main:调用 io.run()
timer1:1 秒到了
timer2:3 秒到了
main:两个定时器都执行完,io.run() 返回

6.5.本示例需要注意的點

timer1_timer2_ 是兩個非同步任務,但本例仍然只有一個執行緒。

因為只有主執行緒呼叫了:

io.run();

所以兩個回撥都在主執行緒裡執行,不會並行執行。


7.示例 7:兩個執行緒同時運行同一個 io_context

7.1.程式目標

這次讓兩個執行緒都呼叫 io.run(),觀察回撥可能由不同執行緒執行。

這個例子用於理解官方 Timer.5 那類程式:

多个线程 run 同一个 io_context
回调函数可能分配给任意一个正在 run 的线程执行

7.2.完整程式碼

#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <chrono>
#include <functional>
#include <iostream>
#include <string>
#include <thread>

void print_timer(const boost::system::error_code& ec, const std::string& name)
{
    if (ec)
    {
        std::cout << name << " 取消:" << ec.message() << std::endl;
        return;
    }

    std::cout << name << " 回调执行,线程 id = "
              << std::this_thread::get_id() << std::endl;
}

void run_io(boost::asio::io_context* io, const std::string& thread_name)
{
    std::cout << thread_name << " 开始调用 io.run(),线程 id = "
              << std::this_thread::get_id() << std::endl;

    io->run();

    std::cout << thread_name << " 的 io.run() 返回" << std::endl;
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
    boost::asio::io_context io;

    // 创建定时器,并设置到期时间。
    boost::asio::steady_timer timer1(io, std::chrono::seconds(1));
    boost::asio::steady_timer timer2(io, std::chrono::seconds(1));

    // 注册异步等待:这一行不会阻塞,回调会在定时器到期后执行。
    timer1.async_wait(std::bind(print_timer,
                                std::placeholders::_1,
                                std::string("timer1")));

    timer2.async_wait(std::bind(print_timer,
                                std::placeholders::_1,
                                std::string("timer2")));

    std::thread t1(std::bind(run_io, &io, std::string("线程1")));
    std::thread t2(std::bind(run_io, &io, std::string("线程2")));

    t1.join();
    t2.join();

    std::cout << "main:两个线程都结束" << std::endl;

    return 0;
}

執行結果:見下方“執行輸出與時間順序”;如果示例涉及定時器、執行緒、網路或外部裝置,具體時間和順序可能會隨環境略有變化。

7.3.編譯執行

g++ demo7_two_threads.cpp -o demo7_two_threads -std=c++17 -lboost_system -pthread
./demo7_two_threads

7.4.執行輸出與時間順序

程式啟動後兩個執行緒幾乎立刻輸出:

线程1 开始调用 io.run(),线程 id = 140000000000001
线程2 开始调用 io.run(),线程 id = 140000000000002

約 1 秒後,兩個 timer 都到期,輸出可能類似:

timer1 回调执行,线程 id = 140000000000001
timer2 回调执行,线程 id = 140000000000002
线程1 的 io.run() 返回
线程2 的 io.run() 返回
main:两个线程都结束

也可能兩個回撥都被同一個執行緒執行。執行緒排程不保證固定。

7.5.本示例需要注意的點

這個示例的重點是:

谁调用 io.run(),谁就有可能执行回调函数。

不是 timer1 固定屬於執行緒 1,timer2 固定屬於執行緒 2。

7.6.std::thread 裡的 std::bind

這句:

std::thread t1(std::bind(run_io, &io, std::string("线程1")));

意思是建立一個執行緒,在新執行緒裡執行:

run_io(&io, "线程1");

這裡也沒有使用 lambda。


8.示例 8:多執行緒下使用 strand 避免回撥併發

8.1.程式目標

如果多個執行緒同時 run() 一個 io_context,不同回撥可能同時執行。
如果這些回撥都要修改同一個成員變數,就可能出現數據競爭。

strand 的作用是:

保证绑定到同一个 strand 上的 handler 不会并发执行。

8.2.完整程式碼

#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <chrono>
#include <functional>
#include <iostream>
#include <thread>

class StrandCounter
{
public:
    explicit StrandCounter(boost::asio::io_context& io)
        : strand_(boost::asio::make_strand(io)),
          timer1_(io, std::chrono::seconds(1)),
          timer2_(io, std::chrono::seconds(1)),
          count_(0)
    {
        // 注册异步等待:这一行不会阻塞,回调会在定时器到期后执行。
        timer1_.async_wait(
            boost::asio::bind_executor(
                strand_,
                std::bind(&StrandCounter::on_timer,
                          this,
                          std::placeholders::_1,
                          1)));

        timer2_.async_wait(
            boost::asio::bind_executor(
                strand_,
                std::bind(&StrandCounter::on_timer,
                          this,
                          std::placeholders::_1,
                          2)));
    }

private:
    void on_timer(const boost::system::error_code& ec, int id)
    {
        if (ec)
        {
            std::cout << "timer" << id << " 取消:" << ec.message() << std::endl;
            return;
        }

        ++count_;

        std::cout << "timer" << id << " 回调执行,count = " << count_
                  << ",线程 id = " << std::this_thread::get_id() << std::endl;
    }

private:
    boost::asio::strand<boost::asio::io_context::executor_type> strand_;
    // 创建定时器,并设置到期时间。
    boost::asio::steady_timer timer1_;
    boost::asio::steady_timer timer2_;
    int count_;
};

void run_io(boost::asio::io_context* io, const std::string& name)
{
    std::cout << name << " 开始 run,线程 id = "
              << std::this_thread::get_id() << std::endl;

    io->run();

    std::cout << name << " run 返回" << std::endl;
}

int main()
{
    // 程序从 main 函数开始执行,下面的语句会按顺序运行。
    // io_context 是 Asio 的事件循环对象,异步任务需要靠它调度。
    boost::asio::io_context io;
    StrandCounter counter(io);

    std::thread t1(std::bind(run_io, &io, std::string("线程1")));
    std::thread t2(std::bind(run_io, &io, std::string("线程2")));

    t1.join();
    t2.join();

    std::cout << "main:结束" << std::endl;

    return 0;
}

執行結果:見下方“執行輸出與時間順序”;如果示例涉及定時器、執行緒、網路或外部裝置,具體時間和順序可能會隨環境略有變化。

8.3.編譯執行

g++ demo8_strand.cpp -o demo8_strand -std=c++17 -lboost_system -pthread
./demo8_strand

8.4.執行輸出與時間順序

程式啟動後兩個執行緒開始 run()

线程1 开始 run,线程 id = 140000000000001
线程2 开始 run,线程 id = 140000000000002

約 1 秒後兩個 timer 到期:

timer1 回调执行,count = 1,线程 id = 140000000000001
timer2 回调执行,count = 2,线程 id = 140000000000002
线程1 run 返回
线程2 run 返回
main:结束

執行緒 id 和 timer 順序不固定,但 count 不會出現兩個回撥同時改它的併發問題。

8.5.本示例需要注意的點

strand 不是讓所有回撥固定在同一個執行緒。
strand 是保證這些回撥“不同時執行”。

也就是說,可能是:

timer1 在线程1执行
timer2 在线程2执行

但不會出現:

timer1 和 timer2 同时进入 on_timer()

8.6.bind_executor 說明

這段:

boost::asio::bind_executor(strand_, handler)

作用:把 handler 繫結到 strand_ 這個執行器上。

以後 handler 執行時,會遵守這個 strand 的序列化規則。


9.本節總結

你現在應該記住:

  1. timer.wait() 是同步阻塞。
  2. timer.async_wait() 是註冊非同步任務,不阻塞。
  3. io.run() 才是事件迴圈的入口。
  4. 一個執行緒 run(),回撥就在這個執行緒裡順序執行。
  5. 多個執行緒 run(),回撥可能被不同執行緒執行。
  6. 多執行緒共享資料時,用 strand 避免回撥併發。
  7. 類裡繫結成員函式的標準寫法是:
std::bind(&ClassName::member_function,
          this,
          std::placeholders::_1,
          std::placeholders::_2)