28、跨平台并发编程:Windows线程API、POSIX线程(pthreads)、跨平台封装策略、使用Boost.Thread

说实话,跨平台并发编程这个话题,我当年刚入行时踩过不少坑。那时候公司产品要同时跑在Windows和Linux上,我天真地以为写两套线程代码就完事了。结果呢?维护成本直接翻倍,还经常出现「Windows上跑得好好的,Linux上一跑就崩」的诡异问题。

后来我学乖了。跨平台并发编程,说白了就三条路:要么直接怼系统API(Windows线程API或pthreads),要么自己封装一层,要么用现成的库(比如Boost.Thread)。今天咱们就把这三条路都走一遍,看看各自的门道。

Windows线程API:CreateThread与它的朋友们

Windows的线程API,核心就是CreateThread。我刚开始用的时候,觉得这函数参数真多,后来用顺手了,反而觉得它设计得挺严谨。

// Windows线程示例
#include <windows.h>
#include <iostream>

DWORD WINAPI ThreadProc(LPVOID lpParam) {
    int* data = static_cast<int*>(lpParam);
    std::cout << "线程收到数据: " << *data << std::endl;
    return 0;
}

int main() {
    int value = 42;
    HANDLE hThread = CreateThread(
        NULL,           // 安全属性,默认NULL
        0,              // 栈大小,0表示默认
        ThreadProc,     // 线程函数
        &value,         // 传给线程的参数
        0,              // 创建标志,0表示立即运行
        NULL            // 线程ID,不需要可以传NULL
    );

    if (hThread == NULL) {
        std::cerr << "创建线程失败,错误码: " << GetLastError() << std::endl;
        return 1;
    }

    WaitForSingleObject(hThread, INFINITE);
    CloseHandle(hThread);
    return 0;
}
⚠️ 避坑指南
我曾经在项目里直接用_beginthreadex替代CreateThread,因为前者能正确初始化C运行时库。如果你在Windows上写C++多线程,建议用_beginthreadex而不是CreateThread,否则可能会遇到内存泄漏或CRT函数调用异常。

Windows还提供了WaitForMultipleObjectsSetEventCreateMutex等同步原语。说实话,这套API虽然功能强大,但命名风格跟C++标准库差得有点远,跨平台移植时改起来挺痛苦的。

POSIX线程(pthreads):Linux/Unix世界的标准

pthreads是POSIX标准定义的线程接口,Linux、macOS、FreeBSD都支持。它的设计哲学跟Windows API完全不同——更简洁,更「函数式」。

// POSIX线程示例
#include <pthread.h>
#include <iostream>

void* ThreadProc(void* arg) {
    int* data = static_cast<int*>(arg);
    std::cout << "线程收到数据: " << *data << std::endl;
    return nullptr;
}

int main() {
    pthread_t thread;
    int value = 42;

    int ret = pthread_create(&thread, nullptr, ThreadProc, &value);
    if (ret != 0) {
        std::cerr << "创建线程失败,错误码: " << ret << std::endl;
        return 1;
    }

    pthread_join(thread, nullptr);
    return 0;
}

你看,pthreads的接口更统一:pthread_createpthread_joinpthread_mutex_lock……所有函数都带pthread_前缀,一眼就能认出是线程相关。我个人觉得这种命名规范比Windows的CreateThreadWaitForSingleObject更容易记忆。

💡 小技巧
在Linux上编译pthreads程序时,记得加-lpthread链接选项。我见过不少新手忘了这个,结果链接报错一脸懵。

跨平台封装策略:自己动手,丰衣足食

如果你不想依赖第三方库,可以自己封装一层。核心思路就是:用预处理器宏区分平台,然后提供统一的接口。

// 跨平台线程封装示例
#ifdef _WIN32
    #include <windows.h>
    using ThreadHandle = HANDLE;
    using ThreadFunc = DWORD (WINAPI*)(LPVOID);
#else
    #include <pthread.h>
    using ThreadHandle = pthread_t;
    using ThreadFunc = void* (*)(void*);
#endif

class Thread {
public:
    template <typename Func, typename... Args>
    bool Start(Func&& func, Args&&... args) {
        // 内部实现:根据平台调用CreateThread或pthread_create
        // 这里省略具体实现细节
    }

    void Join() {
        // 内部实现:根据平台调用WaitForSingleObject或pthread_join
    }

private:
    ThreadHandle handle_;
};

这种封装方式,我在一个嵌入式项目中用过。当时项目要同时支持Windows和Linux,但又不允许引入Boost这种重量级库。自己封装的好处是轻量、可控,坏处是——你得把同步原语(互斥锁、条件变量、信号量)也全封装一遍。嗯,工作量不小。

📌 封装策略要点
  • #ifdef _WIN32 / #else区分平台
  • 统一线程函数签名(比如都用void* (*)(void*)
  • 封装同步原语时注意语义一致性(比如Windows的CriticalSection对应pthreads的mutex
  • 考虑异常安全:确保线程创建失败时资源正确释放

使用Boost.Thread:成熟稳定的跨平台方案

Boost.Thread是C++11标准线程库的前身,也是很多老项目的首选。它的接口跟std::thread非常相似,但多了一些实用功能(比如thread_groupbarrier)。

// Boost.Thread示例
#include <boost/thread.hpp>
#include <iostream>

void Worker(int id) {
    std::cout << "线程 " << id << " 开始工作" << std::endl;
    boost::this_thread::sleep(boost::posix_time::seconds(1));
    std::cout << "线程 " << id << " 工作结束" << std::endl;
}

int main() {
    boost::thread_group group;

    for (int i = 0; i < 5; ++i) {
        group.create_thread(boost::bind(Worker, i));
    }

    group.join_all();  // 等待所有线程结束
    return 0;
}

Boost.Thread的thread_group是我特别喜欢的功能。你想想看,如果你要管理10个线程,用std::thread得手动维护一个vector<thread>,然后一个个join。而thread_group直接帮你搞定,一个join_all就完事了。

💡 我的经验
如果你在维护一个老项目,而且已经用了Boost的其他组件(比如Boost.Asio、Boost.Filesystem),那引入Boost.Thread是顺理成章的事。但如果是从零开始的新项目,我建议直接用C++11的std::thread——毕竟标准库不需要额外依赖,而且现代编译器对C++11的支持已经很好了。

知识体系总览

下面这张图,是我自己总结的跨平台并发编程知识体系。你可以把它当作一张「地图」,遇到具体问题时知道该往哪个方向查。

跨平台并发编程知识体系 跨平台并发编程 Windows线程API CreateThread / _beginthreadex WaitForSingleObject CriticalSection / Mutex Event / Semaphore POSIX线程 (pthreads) pthread_create / pthread_join pthread_mutex_lock pthread_cond_wait pthread_barrier 跨平台封装策略 #ifdef _WIN32 / #else 统一接口设计 同步原语封装 异常安全处理 Boost.Thread thread_group / barrier boost::mutex / condition C++11 std::thread std::thread / std::mutex std::condition_variable

如何选择?我的建议

说了这么多,到底该用哪个?我根据实际项目经验,给你几个参考标准:

场景 推荐方案 理由
新项目,C++11及以上 std::thread 标准库,无依赖,现代编译器支持好
老项目,已用Boost Boost.Thread 与现有Boost组件无缝集成
嵌入式/资源受限 自己封装 轻量,可控,无额外依赖
需要高级功能(如barrier) Boost.Thread 标准库没有barrier,Boost有
跨平台移植已有代码 根据目标平台选择 尽量保持原有接口不变
⚠️ 重要提醒
不管你选哪种方案,一定要在项目初期就定好。我见过一个项目,Windows上用std::thread,Linux上用pthreads,结果维护了两年后,代码里到处都是#ifdef,改一个bug要改两个地方。说白了,跨平台不是「能跑就行」,而是「好维护才行」。

好了,关于跨平台并发编程,今天就聊到这儿。记住:没有银弹,只有最适合你项目的方案。如果你还在犹豫,不妨从std::thread开始——毕竟它是标准,未来也不会过时。


公众号:蓝海资料掘金营,微信deep3321