C++ Boost.Asio 入门 之 Hello World
C++ Boost.Asio 入门
简介
Boost.Asio是一个跨平台的、主要用于网络和其他一些底层输入/输出编程的C++库。
开发环境搭建
安装
先看下是否已经安装,没有安装的话安装一下。
pacman -Ss mingw-w64-ucrt-x86_64-boost
配置CmakeLists.txt
cmake_minimum_required(VERSION 3.10.0)
project(learn01 VERSION 0.1.0 LANGUAGES C CXX)
# ✅ 设置 C++ 标准
set(CMAKE_CXX_STANDARD 26) # 使用 C++26 标准
set(CMAKE_CXX_STANDARD_REQUIRED ON) # 强制使用指定标准
set(CMAKE_CXX_EXTENSIONS OFF) # 禁用编译器扩展(使用纯标准)
# 如果是单配置生成器(Makefile/Ninja),在未指定时默认使用 Release
if(NOT CMAKE_CONFIGURATION_TYPES AND NOT CMAKE_BUILD_TYPE)
message(STATUS "No build type specified, default to Release")
set(CMAKE_BUILD_TYPE "Release" CACHE STRING "Build type" FORCE)
endif()
message("CMAKE_BUILD_TYPE: ${CMAKE_BUILD_TYPE}")
# 查找源文件
file(GLOB_RECURSE SOURCES CONFIGURE_DEPENDS
"src/*.cpp"
"src/*.c"
)
add_executable(learn01 main.cpp ${SOURCES})
# 设置头文件包含路径
target_include_directories(${CMAKE_PROJECT_NAME}
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/learn01
PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/learn02
)
# —— 为目标按配置设置编译选项 ——
# Release: -O2 (GCC/Clang) 或 /O2 (MSVC),并加上 -DNDEBUG
# Debug: 指定更合适的 Debug 标志
if (MSVC)
message(STATUS "Using MSVC compiler settings")
target_compile_options(learn01 PRIVATE
$<$<CONFIG:Release>:/O3 /DNDEBUG>
$<$<CONFIG:Debug>:/Od /Zi>
)
else()
message(STATUS "Using GCC/Clang compiler settings")
target_compile_options(learn01 PRIVATE
$<$<CONFIG:Release>:-O3 -DNDEBUG>
$<$<CONFIG:Debug>:-Og -g>
)
endif()
# 可选:对 Release 开启跨模块优化(LTO),如果编译器/链接器支持
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_RELEASE ON)
# 5. 链接 Boost 库
find_package(Boost CONFIG REQUIRED COMPONENTS system)
# 链接 Boost 库
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE Boost::system)
# Windows 需要额外链接 Winsock2、Mswsock
if(WIN32)
target_link_libraries(${CMAKE_PROJECT_NAME} PRIVATE ws2_32 Mswsock)
endif()
include(CTest)
enable_testing()
set(CPACK_PROJECT_NAME ${PROJECT_NAME})
set(CPACK_PROJECT_VERSION ${PROJECT_VERSION})
include(CPack)
Hello World
learn02.hpp
#ifndef LEARN02_HPP
#define LEARN02_HPP
#include <iostream>
#include <string>
#include <vector>
#include <coroutine>
#include <exception>
#include <thread>
#include <chrono>
#include <future>
#include <boost/asio.hpp>
#include <boost/asio/awaitable.hpp>
#include <boost/asio/use_awaitable.hpp>
#include <boost/asio/co_spawn.hpp>
#include <boost/asio/detached.hpp>
void learn04();
void learn03();
void learn02();
void learn01();
#endif // LEARN02_HPP
learn02.cpp
#include "learn02.hpp"
awaitable<void> async_print(const std::string &message){
auto executor = co_await boost::asio::this_coro::executor;
boost::asio::steady_timer timer(executor);
timer.expires_after(std::chrono::seconds(1));
co_await timer.async_wait(boost::asio::use_awaitable);
std::cout << "message: " << message << "\n";
}
void learn04(){
boost::asio::io_context ioContext(1);
boost::asio::co_spawn(ioContext, async_print("Hello World From Coroutine!"), boost::asio::detached);
ioContext.run();
}
执行代码,你会发现正确打印出Hello World From Coroutine!,你非常高兴,于是你又多执行了几次,突然出现如下结果:
PS D:\SoftWare\LanguageProjects\C++Projects\learn01\build> ."D:/SoftWare/LanguageProjects/C++Projects/learn01/build/learn01.exe"
message: Hello World From Coroutine!
PS D:\SoftWare\LanguageProjects\C++Projects\learn01\build> ."D:/SoftWare/LanguageProjects/C++Projects/learn01/build/learn01.exe"
message: `f������@��
PS D:\SoftWare\LanguageProjects\C++Projects\learn01\build>
于是你一脸懵逼

莫慌、冷静冷静!
- 首先将
char*常量通过std::string构造函数隐式转为了std::string类型的一个临时对象 - 然后以
const string&引用右值,可能是线程切换导致引用右值失败,导致message成为一个悬空指针。
这里的原理和原因到底是什么尚且不是很清楚,如果各位大佬清楚的话还请交流分享一下,分享到评论区,感谢!!!👍
寻找原因
执行如下代码
struct Task {
public:
struct promise_type {
promise_type(){
std::cout << "promise_type constructor\n";
}
~promise_type(){
std::cout << "promise_type destructor\n";
}
Task get_return_object() {
std::cout << "get_return_object\n";
return Task(std::coroutine_handle<promise_type>::from_promise(*this));
}
std::suspend_always initial_suspend() {
std::cout << "initial_suspend\n";
return {};
}
std::suspend_always final_suspend() noexcept {
std::cout << "final_suspend\n";
return {};
}
void return_void() {
std::cout << "return_void\n";
}
void unhandled_exception() {
std::cout << "unhandled_exception\n";
}
};
void resume(){
std::lock_guard<std::mutex> lock(mutex); // RAII
if(!handle.done()){
handle.resume(); // 1
} // 临界区
}
Task(std::coroutine_handle<promise_type> coroutineHandle) : handle(coroutineHandle) {
std::cout << "Task constructor\n";
}
~Task(){
handle.destroy();
std::cout << "Task destructor\n";
}
private:
std::coroutine_handle<promise_type> handle; // 指向协程帧
std::mutex mutex; // 保护多线程 resume
};
Task coro(const std::string& msg) {
std::cout << "animalPtr1: " << &msg << "\n";
auto coroThreadId1 = std::this_thread::get_id();
std::cout << "coroThreadId1: " << coroThreadId1 << "\n";
std::cout << "before suspend: " << msg << "\n";
co_await std::suspend_always{}; // <-- 挂起点
std::cout << "animalPtr2: " << &msg << "\n";
auto coroThreadId2 = std::this_thread::get_id();
std::cout << "coroThreadId2: " << coroThreadId2 << "\n";
std::cout << "middle1 suspend: " << msg << "\n"; // ⚠️ 这里可能悬空
co_await std::suspend_always{}; // <-- 挂起点
std::cout << "animalPtr3: " << &msg << "\n";
auto coroThreadId3 = std::this_thread::get_id();
std::cout << "coroThreadId3: " << coroThreadId3 << "\n";
std::cout << "middle2 suspend: " << msg << "\n"; // ⚠️ 这里可能悬空
co_await std::suspend_always{}; // <-- 挂起点
std::cout << "after suspend: " << msg << "\n"; // ⚠️ 这里可能悬空
auto coroThreadId4 = std::this_thread::get_id();
std::cout << "coroThreadId4: " << coroThreadId4 << "\n";
std::cout << "animalPtr4: " << &msg << "\n";
}
void learn04(){
auto mainThreadId1 = std::this_thread::get_id();
std::cout << "mainThreadId1: " << mainThreadId1 << "\n";
Task task = coro("Hello Temporary String!"); // 传临时对象
std::thread([&]() -> void {
auto subThreadId1 = std::this_thread::get_id();
std::cout << "subThreadId1: " << subThreadId1 << "\n";
task.resume();
}).detach();
std::this_thread::sleep_for(std::chrono::seconds(1));
std::thread([&]() -> void {
auto subThreadId2 = std::this_thread::get_id();
std::cout << "subThreadId2: " << subThreadId2 << "\n";
task.resume();
}).detach();
std::this_thread::sleep_for(std::chrono::seconds(1));
std::thread([&]() -> void {
auto subThreadId3 = std::this_thread::get_id();
std::cout << "subThreadId3: " << subThreadId3 << "\n";
task.resume();
}).detach();
std::this_thread::sleep_for(std::chrono::seconds(1));
task.resume(); // 回到主线程,前提是保证它最后调用
auto mainThreadId2 = std::this_thread::get_id();
std::cout << "mainThreadId2: " << mainThreadId2 << "\n";
}
/*
执行结果:
mainThreadId1: 1
promise_type constructor
get_return_object
Task constructor
initial_suspend
subThreadId1: 2
animalPtr1: 0x6611bff8d0
coroThreadId1: 2
sefore suspend: Hello Te�H�
subThreadId2: 3
animalPtr2: 0x6611bff8d0
coroThreadId2: 3
siddle1 suspend: Hello Te�H�
subThreadId3: 4
animalPtr3: 0x6611bff8d0
coroThreadId3: 4
middle2 suspend: H�).����f
after suspend: H�).����f
coroThreadId4: 1
animalPtr4: 0x6611bff8d0
return_void
final_suspend
mainThreadId2: 1
promise_type destructor
Task destructor
*/
因为resume不是线程安全的,在多线程环境下需要保证线程安全。会发现可以使得每次输出的msg变量一致。
至于为什么msg会是悬空指针,猜测是创建线程执行回调然后执行resume的时候,临时对象以经被析构了。
问题复现
struct Task {
public:
struct promise_type {
promise_type(){
std::cout << "promise_type constructor\n";
}
~promise_type(){
std::cout << "promise_type destructor\n";
}
Task get_return_object() {
std::cout << "get_return_object\n";
return Task(std::coroutine_handle<promise_type>::from_promise(*this));
}
std::suspend_always initial_suspend() {
std::cout << "initial_suspend\n";
return {};
}
std::suspend_always final_suspend() noexcept {
std::cout << "final_suspend\n";
return {};
}
void return_void() {
std::cout << "return_void\n";
}
void unhandled_exception() {
std::cout << "unhandled_exception\n";
}
};
void resume(){
if(!handle.done()){
handle.resume(); // 2
}
}
Task(std::coroutine_handle<promise_type> coroutineHandle) : handle(coroutineHandle) {
std::cout << "Task constructor\n";
}
~Task(){
handle.destroy();
std::cout << "Task destructor\n";
}
private:
std::coroutine_handle<promise_type> handle; // 指向协程帧
};
Task coro(const std::string& msg) {
auto coroThreadId1 = std::this_thread::get_id();
std::cout << "coroThreadId1: " << coroThreadId1 << "\n";
std::cout << "before suspend: " << msg << "\n";
co_await std::suspend_always{}; // <-- 挂起点
auto coroThreadId2 = std::this_thread::get_id();
std::cout << "coroThreadId2: " << coroThreadId2 << "\n";
std::cout << "middle1 suspend: " << msg << "\n"; // ⚠️ 这里可能悬空
co_await std::suspend_always{}; // <-- 挂起点
auto coroThreadId3 = std::this_thread::get_id();
std::cout << "coroThreadId3: " << coroThreadId3 << "\n";
std::cout << "middle2 suspend: " << msg << "\n"; // ⚠️ 这里可能悬空
co_await std::suspend_always{}; // <-- 挂起点
std::cout << "after suspend: " << msg << "\n"; // ⚠️ 这里可能悬空
auto coroThreadId4 = std::this_thread::get_id();
std::cout << "coroThreadId4: " << coroThreadId4 << "\n";
}
void learn04(){
// boost::asio::io_context ioContext(1);
// boost::asio::co_spawn(ioContext, async_print("Hello World From Coroutine!"), boost::asio::detached);
// ioContext.run();
auto mainThreadId1 = std::this_thread::get_id();
std::cout << "mainThreadId1: " << mainThreadId1 << "\n";
Task task = coro("Hello Temporary String!"); // 传临时对象
std::thread([&]() -> void {
auto subThreadId1 = std::this_thread::get_id();
std::cout << "subThreadId1: " << subThreadId1 << "\n";
task.resume();
}).detach();
std::this_thread::sleep_for(std::chrono::seconds(1));
std::thread([&]() -> void {
auto subThreadId2 = std::this_thread::get_id();
std::cout << "subThreadId2: " << subThreadId2 << "\n";
task.resume();
}).detach();
std::this_thread::sleep_for(std::chrono::seconds(1));
std::thread([&]() -> void {
auto subThreadId3 = std::this_thread::get_id();
std::cout << "subThreadId3: " << subThreadId3 << "\n";
task.resume();
}).detach();
std::this_thread::sleep_for(std::chrono::seconds(1));
task.resume(); // 回到主线程
auto mainThreadId2 = std::this_thread::get_id();
std::cout << "mainThreadId2: " << mainThreadId2 << "\n";
}
完美复现!
你会发现和线程切换是有关系的。甚至debug也会出现这种问题。
解决办法:
- 将
std::string&引用参数改为std::string,临时std::string对象会调用拷贝构造然后传递给coro函数。 - 使用如下调用
std::string message = "Hello Temporary String!";
Task task = coro(message);
你会发现这两种解决办法有一个共同点:
就是让msg参数的生命周期>=函数coro的生命周期。
再来看一张有趣的图

根据打印信息可以得出结论:resume在哪个线程被调用,红色框的代码就在哪个线程执行。这就是协程使用的技术,它就是大名鼎鼎的状态机!!!
修改learn02.cpp
static awaitable<void> async_print(const std::string message){
std::cout << "message1: " << message << "\n";
auto executor = co_await boost::asio::this_coro::executor;
std::cout << "message2: " << message << "\n";
boost::asio::steady_timer timer(executor);
timer.expires_after(std::chrono::seconds(1));
std::cout << "message3: " << message << "\n";
co_await timer.async_wait(boost::asio::use_awaitable);
std::cout << "message4: " << message << "\n";
}
void learn04(){
boost::asio::io_context ioContext(1);
boost::asio::co_spawn(ioContext, async_print("Hello World From Coroutine!"), boost::asio::detached);
ioContext.run();
}
co_spawn是提交一个协程任务。
带返回值的情况
static awaitable<std::string> async_print(const std::string message){
std::cout << "message1: " << message << "\n";
auto executor = co_await boost::asio::this_coro::executor;
std::cout << "message2: " << message << "\n";
boost::asio::steady_timer timer(executor);
timer.expires_after(std::chrono::seconds(1));
std::cout << "message3: " << message << "\n";
co_await timer.async_wait(boost::asio::use_awaitable);
std::cout << "message4: " << message << "\n";
co_return "Hello World Coroutine!";
}
void learn04(){
boost::asio::io_context ioContext(1);
auto result = boost::asio::co_spawn(ioContext, async_print("Hello World From Coroutine!"), boost::asio::use_future);
ioContext.run();
std::cout << result.get() << "\n";
}
你会发现使用Boost.Asio库编程真是太爽了!!!

C++ Boost.Asio 入门 之 Hello World
浙公网安备 33010602011771号