Boost.Asio 知识点
2024-10-13 22:53:16

线程模型

有三种使用方式:

  • 一个io_context,一个线程。是线程安全的,相当于同步方式,没有并发。
  • 一个io_context,多个线程。有并发了,但是线程不安全,要处理数据同步问题。
  • 多个线程,每个线程一个io_context。和第一个类似,线程是安全的,且又有并发性。但是要自己去分配io_context

处理程序执行顺序

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void func(int i) {
auto s = std::format("thread {}, func called i = {}", GetCurrentThreadId(), i);
std::cout << s << std::endl;
}

void worker_thread(boost::asio::io_context& ioc) {
ioc.run();
}

void foo3() {
boost::asio::io_context ioc;

for (int i = 0; i < 10; ++i) {
boost::asio::post(ioc, std::bind(func, i));
}

std::this_thread::sleep_for(std::chrono::milliseconds(500));
std::vector<std::thread> threads;
for (int i = 0; i < 3; ++i)
threads.emplace_back(worker_thread, std::ref(ioc));

for (auto& thread : threads) {
thread.join();
}
}

输出结果:

1
2
3
4
5
6
7
8
9
10
thread 8716, func called i = 2
thread 18312, func called i = 3
thread 5140, func called i = 1
thread 8716, func called i = 4
thread 18312, func called i = 5
thread 8716, func called i = 6
thread 5140, func called i = 7
thread 18312, func called i = 8
thread 8716, func called i = 9
thread 11660, func called i = 0

由此可以得知 boost::asio::post 只负责投递任务,不保证执行顺序。
如果需要顺序执行,就要用到 io_context::strand

1
2
3
4
5
6
boost::asio::io_context ioc;
boost::asio::io_context::strand strand{ioc};

for (int i = 0; i < 10; ++i) {
boost::asio::post(strand, std::bind(func, i));
}

输出结果:

1
2
3
4
5
6
7
8
9
10
thread 17548, func called i = 0
thread 18284, func called i = 1
thread 18284, func called i = 2
thread 18284, func called i = 3
thread 18284, func called i = 4
thread 18284, func called i = 5
thread 18284, func called i = 6
thread 18284, func called i = 7
thread 18284, func called i = 8
thread 18284, func called i = 9

注意,不保证在同一个线程中执行,哪个线程抢到就让谁执行。
一般在以下两种情况时考虑使用strand

  1. 多个线程访问非线程安全的共享对象。
  2. 需要保证处理程序的顺序执行。


另外,在新版本中以下方法是已经弃用的,网上很多文章中提到的都是老方法,如果用新版本就可以抛弃它们了。
io_context::post
io_context::strand::post

post or dispatch

boost::asio::postboost::asio::dispatch 都是用于投递任务,两者的区别是什么?
post总是立即返回,确保是异步执行。而dispatch会“有可能”立即执行,什么叫“有可能”呢?看个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
void work() {
std::cout << "work" << std::endl;
}

void foo() {
boost::asio::io_context ioc;
boost::asio::post(ioc, [&ioc]() {
boost::asio::dispatch(ioc, work); // 当前在调用链中,会阻塞执行。
std::cout << "after" << std::endl;
});

ioc.run();
}

输出顺序将是

1
2
work
after

所以我的理解是如果当前处理程序是由io_context调度的,那么会立即执行,否则就和post一样。

相关阅读

Boost.Asio 文档
Linux C/C++ Boost.Asio 的多线程模型