pthread、std::thread 与 std::jthread 能否互操作?
结论
不能笼统地保证所有线程 API 都能互操作。std::thread 和 std::jthread 可以共用 C++ 标准库的同步设施;涉及 pthread 时,还要看具体平台及其 C++ 实现是否支持。
在使用 pthread 后端的常见 Linux C++ 环境中,pthread_create 创建的线程通常可以使用 std::mutex、std::condition_variable 等设施,也可以和 std::thread、std::jthread 创建的线程使用同一个同步对象。GCC 的 libstdc++ 文档介绍了 gthread 抽象层与 pthread 接口的互操作设计,但这不能推广为所有 C++ 实现的保证。GCC 线程实现说明
这里需要区分两个问题:不同来源的线程能否使用同一个同步对象,以及不同 API 的同步对象能否相互替代。在支持的平台上,前者通常可行,但这并不意味着后者也可行。
这些名称分别代表什么?
pthread 是 POSIX Threads API,线程标识类型是 pthread_t;它不是 ISO C++ 标准库中的线程类。
std::thread 是 C++11 引入的线程管理类。std::jthread 是 C++20 引入的线程管理类,支持协作式停止。析构时,如果线程仍可连接,它会请求停止,然后执行 join()。
std::jthread 没有单独的一套互斥量或条件变量。它和 std::thread 都可以使用 <mutex>、<condition_variable> 和 <semaphore> 中的同步设施。
互斥量、信号量和条件变量如何互操作?
| 问题 | 结论与边界 |
|---|---|
std::thread 与 std::jthread 能否共用 std::mutex? | 可以,遵守正常的锁定、解锁和对象生命周期要求即可。 |
pthread 创建的线程能否锁定 std::mutex? | 在支持 pthread 与 C++ 运行时互操作的实现中可以;需要确认目标平台的支持。 |
std::mutex 能否直接当作 pthread_mutex_t 使用? | 不能假定类型或内存布局兼容。 |
| 不同来源的线程能否使用同一个信号量? | 在运行时支持的前提下可以,但所有操作必须针对同一个实际同步对象。 |
std::counting_semaphore 能否直接当作 POSIX sem_t? | 没有这种标准保证,也没有可用于转换的标准化原生句柄接口。 |
pthread 创建的线程能否调用 std::condition_variable::wait()? | 在支持的实现中可以,仍需使用符合接口要求的锁。 |
std::condition_variable 能否直接当作 pthread_cond_t? | 不能假定二者可替换。 |
以条件变量为例,std::condition_variable::wait() 接受 std::unique_lock<std::mutex>,不能直接传入 pthread_mutex_t。等待时还必须检查共享状态,通常使用带谓词的重载来处理虚假唤醒;同时等待同一个条件变量的线程需要使用同一个互斥量。C++ 工作草案:condition_variable
如果需要调用底层 pthread 函数,应先查看实现是否提供相应的 native_handle(),并确认返回类型和允许的操作。C++ 将这类接口是否存在及其语义交由实现定义,因此使用时会依赖具体平台。不要通过 reinterpret_cast 猜测对象布局。C++ 工作草案:原生句柄
__thread 能否用于这些线程?
__thread 是 GCC 等编译器提供的线程局部存储扩展,不是用于选择线程类型的属性。
在编译器、链接器和运行时共同支持的环境中,每个底层线程都有各自的变量实例。采用 pthread 后端时,这一点通常不会因线程由 pthread_create、std::thread 还是 std::jthread 创建而改变。这项能力依赖实现,不能只凭 C++ 标准作保证。GCC:线程局部存储
新写的标准 C++ 代码一般使用:
thread_local int counter = 0;
注意两点:
- 每个线程直接访问
counter时,访问的是自己的实例,不能用它代替共享计数器。 __thread与thread_local的初始化和对象构造、析构规则不能简单视为完全相同,尤其是涉及非平凡 C++ 对象时。
yield 函数是否兼容?
std::this_thread::yield() 作用于当前执行线程。在 pthread 与 C++ 运行时能够互操作的环境中,pthread 创建的线程通常也可以调用它;POSIX 环境还提供 sched_yield()。
两者的调用效果受实现和调度策略影响,不能保证一定切换到某个线程,也不能保证公平性。C++ 明确规定 yield() 不提供同步关系,因此不能靠它修复数据竞争,也不能用它代替互斥量、原子操作和条件变量。C++ 工作草案:this_thread
混用三种线程的示例
下面的示例让三个线程共用一个 std::mutex。前提是目标环境支持 pthread 与 C++ 标准库互操作,并提供 C++20 的 std::jthread。
#include <exception>
#include <iostream>
#include <mutex>
#include <pthread.h>
#include <thread>
std::mutex mutex;
int counter = 0;
void increment()
{
for (int i = 0; i < 10000; ++i) {
std::lock_guard<std::mutex> lock(mutex);
++counter;
}
}
extern "C" void* pthread_entry(void*)
{
increment();
return nullptr;
}
int main()
{
std::jthread a(increment);
std::thread b(increment);
pthread_t c;
const int error = pthread_create(&c, nullptr, pthread_entry, nullptr);
if (error != 0) {
b.join();
a.join();
std::cerr << "pthread_create failed: " << error << '\n';
return 1;
}
b.join();
a.join();
if (pthread_join(c, nullptr) != 0) {
std::terminate();
}
std::cout << counter << '\n';
}
在支持上述配置的 GCC/Linux 环境中,可使用:
g++ -std=c++20 -pthread example.cpp -o example
按代码逻辑,三个线程各增加 10000 次,成功完成后的计数应为 30000。每次修改共享计数器都受到同一个互斥量保护,主线程在所有工作线程结束后读取结果。这是预期行为说明,并非实际测试报告。
线程控制函数有哪些限制?
即使同步对象可以共享,也不能因此任意混用不同 API 来管理线程的所有权和生命周期。
- 用
std::thread或std::jthread管理的线程,应通过相应对象执行join()或detach()。不要绕过对象直接调用pthread_join()或pthread_detach(),以免管理对象保存的状态与底层线程状态脱节。 - 标准库没有提供将现有
pthread_t直接接管为std::thread或std::jthread的标准构造接口。 std::jthread::request_stop()是协作式停止请求,需要线程代码检查停止状态或使用支持停止令牌的等待设施;它不等价于pthread_cancel()。- 调度策略、优先级等操作可能通过实现提供的原生句柄完成,但需要遵守平台权限、句柄有效期和实现文档。
实际接入时,先确认线程后端及运行时的兼容性,再为每一份共享状态选定同步对象,并明确由哪个 API 负责线程的创建和回收。
还有哪些线程或并发接口?
C++ 程序还可能使用 C11 <threads.h> 的 thrd_t、Windows 线程 API、Boost.Thread,以及其他库提供的线程封装,具体取决于平台和工具链。
std::async 是异步任务接口;线程池、任务调度库和协程也是常见的并发工具,但它们不一定为每个任务创建一个操作系统线程。协程支持挂起和恢复,不能仅凭这一点就把它视为另一种线程。
备注:内容仅供参考。
