Skip to content

Wiki

Timers and Asynchronous IO

About 29 min read

Existing English translations were reused. 5 current source blocks remain in Simplified Chinese, without triggering paid translation.

This section is the most important section of the entire Boost.Asio tutorial.
The asynchronous models of serial ports, TCP, and UDP are all similar to timers: first register an asynchronous operation, and then by io_context.run() drive callback execution.

All asynchronous callbacks in this section use std::bind, and lambda is not used for now.


1.Example 1: Ordinary main() Write a synchronous timer in

1.1.Program Objectives

First, write the simplest blocking timer: immediately after the program starts, print a message, then block and wait for 2 seconds, and then print the end message.

1.2.Complete code

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

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

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

    std::println("程序开始:准备等待 2 秒");

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

    std::println("2 秒到了:timer.wait() 返回");

    return 0;
}

Running result : See “Running output and chronological order” below; if the example involves timers, threads, networks, or external devices, the specific timing and order may vary slightly depending on the environment.

1.3.Run output and chronological order

程序开始:准备等待 2 秒

After waiting for about 2 seconds:

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

Full output similar to:

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

1.4.Points to note in this example

This program does not embody "asynchrony", because timer.wait() will directly block the current thread.

In other words, the program is stuck here:

timer.wait();

The next line will not be executed until 2 seconds have elapsed.

1.5.Key Function Description

1.5.1.boost::asio::io_context io;

Function: Creates an Asio event loop object.

In the synchronous wait() example, it seems inconspicuous; but in the asynchronous version, all asynchronous events depend on it for driving.

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

Purpose: Create a timer based on a stable clock, set to expire after 2 seconds.

Parameters:

io

Indicates which io_context manages this timer.

std::chrono::seconds(2)

Indicates expiration 2 seconds after the current time.

1.5.3.timer.wait();

Purpose: block and wait for the timer to expire.

Return value:void.

Features: simple, but it blocks the current thread. If written this way in the main control loop of a robot program, it can easily freeze the entire program.


2.示例 2:普通 main() 里写异步定时器

2.1.Program Objectives

Rewrite Example 1 in asynchronous style:

  1. Register a 2-second timer;
  2. async_wait() does not block;
  3. io.run() begins blocking;
  4. after 2 seconds the callback function is executed;
  5. after no tasks, io.run() returns.

2.2.Complete code

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

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

    std::println("回调函数 on_timer:2 秒到了");
}

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

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

    std::println("main:注册 async_wait");

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

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

    io.run();

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

    return 0;
}

Running result : See “Running output and chronological order” below; if the example involves timers, threads, networks, or external devices, the specific timing and order may vary slightly depending on the environment.

2.3.Run output and chronological order

The program immediately outputs:

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

Then the program will get stuck at:

io.run();

Output after waiting about 2 seconds:

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

Full output similar to:

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

2.4.Points to note in this example

async_wait() It will not wait for 2 seconds. It merely registers the matter of "execute the callback after 2 seconds" with io_context.

What is really being awaited is:

io.run();

Therefore, do not interpret async_wait() as an "asynchronous version of wait." More precisely:

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

2.5.std::bind Description

This line of code:

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

Meaning:

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

on_timerThe function signature must be able to accept this parameter:

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

2.6.Key Function Description

2.6.1.timer.async_wait(handler)

Purpose: register an asynchronous wait operation.

Parameter: a callback function object.

Return value:void.

Note: It does not block waiting for the timer to expire; it is only responsible for registration.

2.6.2.io.run()

Function: Starts the event loop, processing asynchronous tasks and callback functions.

返回值:执行过的 handler 数量,类型通常可以看成 std::size_t。

In this example,io.run() 1 callback was executed, so the return value is 1. It's just that we didn't print the return value.


3.Example 3: Write two asynchronous timers in a plain main().

3.1.Program Objectives

It is hard to get a sense of asynchronous scheduling with just one timer, so this time let's write two timers:

  1. timer1 1 秒后到期;
  2. timer2 3 秒后到期;
  3. 两个 timer 都在同一个 io_context 里;
  4. io.run() 会等两个任务都完成后才返回。

3.2.Complete code

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

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

    std::println("{} 到期,执行回调", name);
}

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

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

    std::println("main:调用 io.run()");

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

    std::println("main:io.run() 返回,一共执行了 {} 个回调", count);

    return 0;
}

Running result : See “Running output and chronological order” below; if the example involves timers, threads, networks, or external devices, the specific timing and order may vary slightly depending on the environment.

3.3.Run output and chronological order

The program immediately outputs:

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

Output after about 1 second:

timer1 到期,执行回调

After about another 2 seconds, that is, about 3 seconds after the program starts, output:

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

Full output similar to:

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

3.4.Points to note in this example

Two timers are not two threads.

In this example there is only one thread, namely the main thread.timer1 and timer2 callbacks are both executed in this main thread that calls io.run().

The actual execution order is:

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

他和std::this_thread::sleep_for()不一样,线程里的睡眠是一个阻塞式延迟,而这里的定时器只是一个事件,类似于中断。

3.5.std::bind Fixed parameters in

This sentence:

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

Equivalent to preparing a function object in advance:

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

_1 is the error code passed in by Asio in the future.

"timer1" are parameters that are fixed right now.


4.Example 4: Regular main() with repeated timers

4.1.Program Objectives

Implement a timer that prints once every 1 second and ends after printing 5 times.

This example is very similar to periodic tasks in robotics, for example:

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

4.2.Complete code

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

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

    ++(*count);
    std::println("第 {} 次定时器回调", *count);

    if (*count < 5)
    {
        // 重新设置下一次到期时间,并再次注册异步等待,使定时器继续周期执行。
        timer->expires_after(std::chrono::seconds(1));
        timer->async_wait(std::bind(repeat_timer,
                                    std::placeholders::_1,
                                    timer,
                                    count));
    }
    else
    {
        std::println("已经执行 5 次,不再重新注册定时器");
    }
}

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::println("main:注册第 1 次定时器");

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

    io.run();

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

    return 0;
}

Running result : See “Running output and chronological order” below; if the example involves timers, threads, networks, or external devices, the specific timing and order may vary slightly depending on the environment.

4.3.Run output and chronological order

The program immediately outputs:

main:注册第 1 次定时器

After about 1 second:

第 1 次定时器回调

Afterward, output once every approximately 1 second:

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

Full output similar to:

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

4.4.Points to note in this example

The repeating timer is not while in the loop sleep.

The asynchronous syntax is:

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

That is, "before each callback ends, decide whether to register the next one."

        // 重新设置下一次到期时间,并再次注册异步等待,使定时器继续周期执行。
        timer->expires_after(std::chrono::seconds(1));
        timer->async_wait(std::bind(repeat_timer,
                                    std::placeholders::_1,
                                    timer,
                                    count));

The above two lines are very important:

timer->expires_after(std::chrono::seconds(1));It resets the timer's expiration time to "1 second from now." It itself does not register a callback and does not block the thread; it merely modifies the timer's expiration time.

timer->async_wait(std::bind(repeat_timer,std::placeholders::_1,timer,count));It determines who will be executed after expiration.

Therefore, these two lines can be understood as: "extend the life of the timer for the next round."

boost::asio::steady_timer timer(io, std::chrono::seconds(1));Simply set the first expiration time to "1 second after now." After the first expiration, if you do not call the above two lines again, the timer's expiration time remains the original point in time, which has already passed.

If you do not useexpires_after()to set a new time, then directly callingasync_wait()will cause the callback to execute almost immediately.

4.5.Pointer Lifetime Description

In this example, the following was passed:

&timer
&count

This is to allow repeat_timer() to modify the same timer and count.

This notation is safe in this example because:

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

However, in engineering projects it is more recommended to encapsulate it as a class; see the next example.


5.Example 5: Writing a single asynchronous timer in a class

5.1.Program Objectives

Change Example 2 to a class-encapsulated version.

Class encapsulation is a more common approach in robotics projects, for example:

SerialDriver 类
TcpClient 类
UdpReceiver 类
RobotController 类

5.2.Complete code

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

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

        // 注册异步等待:这一行不会阻塞,回调会在定时器到期后执行。
        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::println("Printer::on_timer 被取消:{}", ec.message());
            return;
        }

        std::println("Printer::on_timer:2 秒到了");
    }

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

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

    Printer printer(io);

    std::println("main:准备调用 io.run()");

    io.run();

    std::println("main:io.run() 返回");

    return 0;
}

Running result : See “Running output and chronological order” below; if the example involves timers, threads, networks, or external devices, the specific timing and order may vary slightly depending on the environment.

5.3.Run output and chronological order

The program immediately outputs:

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

Output after about 2 seconds:

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

Full output similar to:

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

5.4.Constructor initializer list description

This passage:

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

The part after the colon is called the "constructor initialization list".

Its role is: before entering the constructor braces, directly construct the member variable timer_.

Why must it be written this way here? Because boost::asio::steady_timer does not have a simple form that allows you to default-construct first and then assign. It needs to know which io_context it belongs to at construction time.

5.5.Member Function Binding Instructions

This sentence:

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

The meaning is:

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

Why write &Printer::on_timer?

Because on_timer is a member function, not an ordinary global function. A member function must depend on some object to be called.

Why pass this?

because this represents the current Printer object.

5.6.Common pitfalls

Do not write:

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

Because on_timer is a member function, not a normal function.

Also do not miss this:

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

In this way, it is unclear which object's on_timer() is to be called.


6.Example 6: Two timers in a class

6.1.Program Objectives

The class maintains two timers simultaneously:

  1. timer1_Execute after 1 second;
  2. timer2_Execute after 3 seconds;
  3. Use this example to clearly see that "two asynchronous tasks are not equal to two threads".

6.2.Complete code

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

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

        // 注册异步等待:这一行不会阻塞,回调会在定时器到期后执行。
        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::println("timer1 取消:{}", ec.message());
            return;
        }

        std::println("timer1:1 秒到了");
    }

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

        std::println("timer2:3 秒到了");
    }

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::println("main:调用 io.run()");

    io.run();

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

    return 0;
}

Running result : See “Running output and chronological order” below; if the example involves timers, threads, networks, or external devices, the specific timing and order may vary slightly depending on the environment.

6.3.Run output and chronological order

The program immediately outputs:

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

After about 1 second:

timer1:1 秒到了

After about 3 seconds:

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

Full output similar to:

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

6.4.Points to note in this example

timer1_ and timer2_ are two asynchronous tasks, but this example still has only one thread.

Because only the main thread called:

io.run();

Therefore, both callbacks are executed on the main thread and will not execute in parallel.


7.Example 7: Two threads run the same io_context

7.1.Program Objectives

This time, have both threads call io.run(), and observe that the callback may be executed by different threads.

This example is used to understand programs of the official Timer.5 type:

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

7.2.Complete code

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

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

    std::println("{} 回调执行,线程 id = {}", name, std::this_thread::get_id());
}

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

    io->run();

    std::println("{} 的 io.run() 返回", thread_name);
}

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::println("main:两个线程都结束");

    return 0;
}

Running result : See “Running output and chronological order” below; if the example involves timers, threads, networks, or external devices, the specific timing and order may vary slightly depending on the environment.

7.3.Run output and chronological order

After the program starts, the two threads output almost immediately:

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

After about 1 second, both timers expire, and the output may look like:

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

It is also not necessarily the case that a given timer's callback is executed by a particular thread; this is nondeterministic. It is also possible that both callbacks are executed by the same thread. Thread scheduling does not guarantee consistency.

7.4.Points to note in this example

这个示例的重点是:

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

is not timer1 fixed to thread 1,timer2 fixed to thread 2.

7.5.std::thread in std::bind

This sentence:

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

This means creating a thread and executing the following in the new thread:

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

Lambda is not used here either.


8.Example 8: Using strand in multithreading to avoid callback concurrency

8.1.Program Objectives

If only one thread executes:

io.run();

Then io_context the handlers in can naturally only execute one after another.

However, if multiple threads execute the same:

io.run();

For example:

线程1 → io.run()

线程2 → io.run()

Then io_context different handlers in may be taken and executed simultaneously by different threads.

For example, two timers expire at the same time:

线程1 → timer1 的 handler

线程2 → timer2 的 handler

If both handlers modify the same ordinary member variable:

int count_;

The following may occur:

线程1:++count_

线程2:++count_

are executed simultaneously, resulting in a data race.

Boost.Asio Provided:

strand

It is used to solve this kind of concurrent handler execution problem.

First, remember the most essential sentence:

Handlers bound to the same strand will not execute concurrently with each other.

That is to say, even if:

多个线程同时运行 io_context

Handlers within the same strand can still only:

一个执行完
↓
另一个再执行

8.2.Complete code

#include <boost/asio.hpp>
#include <boost/system/error_code.hpp>
#include <chrono>
#include <functional>
#include <print>
#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 到期后执行的 handler,
        // 通过 bind_executor() 绑定到 strand_。
        timer1_.async_wait(
            boost::asio::bind_executor(
                strand_,
                std::bind(
                    &StrandCounter::on_timer,
                    this,
                    std::placeholders::_1,
                    1)));

        // timer2 的 handler 也绑定到同一个 strand_。
        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::println(
                "timer{} 取消:{}",
                id,
                ec.message());

            return;
        }

        ++count_;

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

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::println(
        "{} 开始 run,线程 id = {}",
        name,
        std::this_thread::get_id());

    io->run();

    std::println("{} run 返回", name);
}

int main()
{
    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::println("main:结束");

    return 0;
}

8.3.What happens if strand does not occur?

Suppose we do not use:

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

Instead, directly:

timer1_.async_wait(
    std::bind(
        &StrandCounter::on_timer,
        this,
        std::placeholders::_1,
        1));

timer2_.async_wait(
    std::bind(
        &StrandCounter::on_timer,
        this,
        std::placeholders::_1,
        2));

At the same time, there are two threads:

线程1 → io.run()

线程2 → io.run()

Then when both timers have expired, it is entirely possible that:

线程1
↓
取到 timer1 handler
↓
进入 on_timer()


线程2
↓
取到 timer2 handler
↓
同时进入 on_timer()

That is:

时间 ─────────────────────→

线程1: [ timer1 handler       ]

线程2:    [ timer2 handler       ]
              ↑
            发生重叠

Both handlers contain:

++count_;

And:

int count_;

Just ordinary int.

Therefore, if two threads modify it simultaneously, a data race will occur.


8.4.After adding strand, what happens?

Let's first create:

strand_(boost::asio::make_strand(io))

You can first understand it as:

On this io_context, additionally establish a "serial execution channel".

Then bind both handlers to:

strand_

For example:

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

At this point:

timer1 handler ──┐
                 ├── 同一个 strand_
timer2 handler ──┘

Thus strand ensures:

timer1 handler

和

timer2 handler

They will not be executed simultaneously.

Possibly:

线程1:
timer1 handler 开始
↓
++count_
↓
timer1 handler 结束

线程2:
timer2 handler 开始
↓
++count_
↓
timer2 handler 结束

It may also be the reverse:

线程2:
timer2 handler

然后

线程1:
timer1 handler

However, the execution times of two handlers will not overlap.

It can be drawn as:

时间 ─────────────────────────→

线程1: [ timer1 handler ]

线程2:                    [ timer2 handler ]

Or:

时间 ─────────────────────────→

线程2: [ timer2 handler ]

线程1:                    [ timer1 handler ]

But it will not:

时间 ─────────────────────────→

线程1: [ timer1 handler       ]

线程2:       [ timer2 handler       ]
                  ↑
                同时执行

8.5.strand Not "fixed threads"

This is one of the most important points for understanding strand.

strand It is not to say:

所有绑定到 strand 的 handler
永远都在线程1执行

For example, it is entirely possible:

timer1 handler
→ 在线程1执行

timer2 handler
→ 在线程2执行

As long as they:

不同时执行

That is allowed.

Therefore:

strand
≠ 指定一个固定线程

Instead:

strand
= 保证一组 handler 串行执行

In other words:

What strand manages is "execution times must not overlap," not "which thread execution must occur on."


8.6.strand is also not a new thread

strand You don't know it yourself:

创建一个线程

It still relies on:

io.run();

those threads to actually execute the handler.

In this example, the only thing that still actually executes the handler is:

线程1

线程2

All strand does is:

控制同一组 handler 的执行关系

It can be understood as:

io_context
        ↓
 ┌──────┴──────┐
线程1          线程2
   \             /
    \           /
      strand
        ↓
handler A
handler B
handler C

The handler is still run by the thread that executes io.run(), except that the strand does not allow two handlers belonging to it to run simultaneously.


8.7.Why can the count_ here do without a mutex?

In this example:

++count_;

Only in:

on_timer()

executed in.

And the two on_timer() corresponding handlers:

timer1 handler

timer2 handler

are all bound to the same:

strand_

So they do not execute concurrently.

Therefore:

++count_;

nor will it be executed by two threads simultaneously.

Therefore, there is no need to write here:

std::mutex mutex_;

std::scoped_lock lock(mutex_);
++count_;

strand has passed:

串行化 handler

This avoids concurrent access here.


8.8.But strand does not automatically protect the entire object

This point is also very important.

It cannot be because the class contains:

strand_

Consider it as:

StrandCounter 整个对象自动线程安全

strand can only guarantee:

Handlers dispatched through this strand do not run concurrently with one another.

For example, in this case:

timer1 handler
→ strand_

timer2 handler
→ strand_

So they access:

count_

is safe.

But if another thread directly modifies:

count_;

Or another handler that is not bound to strand_ modifies:

count_;

Then the strand cannot manage it.

For example:

timer1 handler
→ strand_
→ 修改 count_

另一个普通线程
→ 直接修改 count_

These two may still occur concurrently.

Therefore, when using a strand to protect a certain state, it is necessary to ensure:

All related operations that may concurrently access this state follow the serialization rules of the same strand.


8.9.bind_executor()What is it used for?

This passage:

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

It can be understood as:

Bind the executor of this handler to strand_.

For example:

timer1_.async_wait(
    boost::asio::bind_executor(
        strand_,
        std::bind(
            &StrandCounter::on_timer,
            this,
            std::placeholders::_1,
            1)));

Originally, it could be understood as:

timer1 到期
↓
handler 交给 io_context
↓
某个正在 run() 的线程执行

After adding strand, it becomes:

timer1 到期
↓
handler 要经过 strand_
↓
strand_ 保证它不会和
同一个 strand 中的其他 handler 并发
↓
再由某个正在 run() 的线程执行

Therefore:

bind_executor(strand_, handler)

The most intuitive understanding is:

Make this handler follow the serial execution rules of strand_.


8.10.Run output and chronological order

After the program starts:

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

Both threads are currently at:

io->run();

Wait for and execute Asio events.

After about 1 second:

timer1

timer2

All have expired.

Possible output:

timer1 回调执行,count = 1,线程 id = 140000000000001
timer2 回调执行,count = 2,线程 id = 140000000000002

线程1 run 返回
线程2 run 返回

main:结束

It may also be:

timer2 回调执行,count = 1,线程 id = 140000000000002
timer1 回调执行,count = 2,线程 id = 140000000000001

These are all normal.

Because strand does not guarantee:

timer1 一定先执行

Nor is it guaranteed that:

timer1 一定在线程1
timer2 一定在线程2

What strand truly guarantees is:

Two handlers bound to the same strand will not execute simultaneously.


8.11.strand and mutex What is the difference in approach between

If you do not use strand, you can also do it yourself:

std::mutex mutex_;

Then:

void on_timer(...)
{
    std::scoped_lock lock(mutex_);

    ++count_;
}

Data races can also be avoided.

The two approaches are as follows:

mutex

→ handler 可以同时开始执行

→ 但访问共享状态时竞争锁

And:

strand

→ 从 handler 调度层面就不让它们并发执行

So in Boost.Asio, if a group of asynchronous handlers:

本来就应该按串行方式处理同一个对象状态

strand is often a very natural design.

For example:

串口读回调

串口写完成回调

定时器回调

连接状态回调

If they all operate on the internal state of the same connection object, these handlers can be made to pass through the same strand uniformly.

Summary:

So a mutex is very suitable: within a large function, only a very small block of shared data needs protection.

Whereas strand is more suitable: a series of asynchronous events for a single object should inherently be processed one at a time.


8.12.The most important conclusion of this example

If a io_context has only one thread calling it:

io.run();

Then the handler itself will not execute concurrently.

But if:

多个线程
↓
同时 io.run()

Then different handlers may:

在不同线程上同时执行

At this point:

strand

A group of handlers can be serialized.

Ultimately, it can be written as:

多个线程 run(io_context)

        ↓

没有 strand

不同 handler
可能同时执行
        ↓
访问共享状态时
可能产生 data race

Use the same strand:

多个线程 run(io_context)

        ↓

handler 可以由不同线程执行

        ↓

但是同一个 strand 中的 handler
不会并发执行

        ↓

相关状态访问被串行化

So remember this in one sentence:

strand is the handler serialization mechanism provided by Boost.Asio: it does not fix threads, nor does it create threads; it only guarantees that handlers bound to the same strand will not execute concurrently with one another.

9.Summary of this section

You should now remember:

  1. timer.wait() is synchronous and blocking.
  2. timer.async_wait() registers an asynchronous task and does not block.
  3. io.run() is the entry point of the event loop.
  4. a single thread run() and callbacks execute sequentially in this thread.
  5. multiple threads run() and callbacks may be executed by different threads.
  6. when multiple threads share data, use strand to avoid callback concurrency.
  7. the standard way to bind a member function in a class is:
std::bind(&ClassName::member_function,
          this,
          std::placeholders::_1,
          std::placeholders::_2)