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> 

于是你一脸懵逼
image
莫慌、冷静冷静!

  1. 首先将char*常量通过std::string构造函数隐式转为了std::string类型的一个临时对象
  2. 然后以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也会出现这种问题。
解决办法:

  1. std::string&引用参数改为std::string,临时std::string对象会调用拷贝构造然后传递给coro函数。
  2. 使用如下调用
std::string message = "Hello Temporary String!";
Task task = coro(message);

你会发现这两种解决办法有一个共同点:
就是让msg参数的生命周期>=函数coro的生命周期。
再来看一张有趣的图
image
根据打印信息可以得出结论: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库编程真是太爽了!!!

posted @ 2025-09-07 19:03  爱情丶眨眼而去  阅读(13)  评论(0)    收藏  举报