文章目录
1. POSIX 线程库概述
POSIX 线程库提供了一系列以 pthread_
开头的函数,用于线程的创建、终止、同步与控制。使用 pthread 库的基本步骤:
- 引入头文件:
#include <pthread.h>
- 编译时链接 pthread 库:
g++ main.cpp -o main -lpthread
这里的 -lpthread
告诉编译器链接 pthread 库。
2. 创建线程
线程创建的核心函数是 pthread_create
:
int pthread_create(
pthread_t *thread,
const pthread_attr_t *attr,
void *(*start_routine)(void*),
void *arg
);
参数说明:
thread
:用于返回线程 ID。attr
:线程属性,一般可置为NULL
表示使用默认属性。start_routine
:线程函数地址,线程启动后执行的函数。arg
:传给线程函数的参数。
返回值:成功返回 0
,失败返回错误码。
示例
void *routine(void *buf)
{
std::string str = static_cast<char *>(buf);
int cnt = 5;
while (cnt--)
{
std::cout << "我是子线程:" << str << std::endl;
}
return (void *)100;
}
int main()
{
pthread_t tid;
pthread_create(&tid, nullptr, routine, (void *)"thread-1");
int cnt = 5;
while (cnt--)
{
std::cout << "我是主线程" << std::endl;
}
void *ret;
pthread_join(tid, &ret);
std::cout << "线程返回值:" << (long)ret << std::endl;
return 0;
}
3. 获取线程 ID
POSIX 提供了 pthread_self
函数获取当前线程 ID:
pthread_t pthread_self(void);
pthread_t
是线程库定义的类型,用于唯一标识进程内的线程。它是进程内唯一的标识,由 pthread 库维护,和操作系统内核的线程 ID 不完全一样。通过 ps -aL
可以查看系统层面的线程 ID(LWP)。
$ ps -aL | grep mythread
4. 线程传参与返回值详解
示例代码
#include <iostream>
#include <pthread.h>
// 任务类
class Test {
public:
Test(int a, int b) : _a(a), _b(b) {}
int add() { return _a + _b; }
~Test() {}
private:
int _a;
int _b;
};
// 返回值封装类
class Result {
public:
Result(int ret) : _ret(ret) {}
int getRet() { return _ret; }
~Result() {}
private:
int _ret;
};
// 线程执行函数
void *routine(void *args) {
Test* t = static_cast<Test*>(args); // 转回 Test*
int ret = t->add(); // 执行计算
Result* r = new Result(ret); // 封装返回值
delete t; // 释放任务对象
return (void *)r; // 返回给主线程
}
int main() {
Test *t = new Test(10, 20); // 在堆上分配任务对象
pthread_t tid;
pthread_create(&tid, nullptr, routine, (void *)t); // 传递任务指针
Result* ret = nullptr;
pthread_join(tid, (void **)&ret); // 获取线程返回值
std::cout << "线程返回值:" << ret->getRet() << std::endl;
delete ret; // 释放返回值对象
return 0;
}
1. 线程参数传递:任意类型
原理:
pthread_create
的第四个参数是void* arg
,可以传任意类型的指针。- 在线程函数内部,我们通过类型转换将
void*
转回原始类型。 - 参数对象最好在堆上分配,避免栈对象在主线程作用域结束后被释放。
代码解析:
Test *t = new Test(10, 20); // 堆上分配任务对象
pthread_create(&tid, nullptr, routine, (void *)t); // 将任务对象指针传入线程
- 这里将
Test*
转换为void*
传入线程。 - 在线程函数中,再用
static_cast
转回Test*
:
Test* t = static_cast<Test*>(args);
✅ 注意:
static_cast
是类型安全的转换,确保线程内可以正确访问对象。
2. 线程返回值:任意类型
原理:
- 线程函数的返回类型是
void*
,可以返回任意指针。 - 返回值对象也最好在堆上分配,以保证主线程拿到时对象依然有效。
- 主线程通过
pthread_join
获取返回值,并转换回原始类型使用。
代码解析:
int ret = t->add(); // 执行任务
Result* r = new Result(ret); // 封装返回值
return (void *)r; // 返回给主线程
- 主线程获取返回值:
Result* ret = nullptr;
pthread_join(tid, (void **)&ret); // 获取线程返回值
std::cout << "线程返回值:" << ret->getRet() << std::endl;
- 使用完返回值后记得
delete
释放内存。
5. 线程终止
1)通过线程入口函数 return 终止
- 每个线程都有自己的入口函数(
start_routine
)。 - 当线程函数执行
return
时,线程就会终止。 - 注意:不能在子线程中调用
exit()
来终止线程,因为exit()
是 终止整个进程,会导致主线程也退出。
示例:
void* thread_func(void* arg) {
printf("线程开始执行\n");
return (void*)123; // 返回值可被 pthread_join 获取
}
int main() {
pthread_t tid;
void* ret;
pthread_create(&tid, NULL, thread_func, NULL);
pthread_join(tid, &ret);
printf("线程返回值: %ld\n", (long)ret);
}
运行结果:
线程开始执行
线程返回值: 123
2)使用 pthread_exit() 终止线程
pthread_exit(void *value_ptr)
可以显式地终止当前线程。value_ptr
是线程退出时返回给pthread_join
的指针,可以传递结果。- 与
return
不同的是,pthread_exit
可以在任何地方调用(不仅限于函数末尾),线程立即终止,但进程继续运行。
示例:
void* thread_func(void* arg) {
int* ret = (int*)malloc(sizeof(int));
*ret = 456;
pthread_exit((void*)ret); // 终止线程并返回值
}
int main() {
pthread_t tid;
void* ret;
pthread_create(&tid, NULL, thread_func, NULL);
pthread_join(tid, &ret);
printf("线程返回值: %d\n", *(int*)ret);
free(ret);
}
运行结果:
线程返回值: 456
⚠️ 注意:传递给 pthread_exit
的指针不能是局部变量,因为线程终止后栈会被释放。
3)线程被取消 (pthread_cancel)
- 一个线程可以被 其他线程取消,使用
pthread_cancel(thread_id)
。 - 被取消的线程退出时,
pthread_join
返回的值是一个特殊常量PTHREAD_CANCELED
。 - 线程在被取消时,不会返回自己正常的返回值,而是标识被取消。
示例:
void* thread_func(void* arg) {
while (1) {
printf("线程正在运行...\n");
sleep(1);
}
return NULL;
}
int main() {
pthread_t tid;
void* ret;
pthread_create(&tid, NULL, thread_func, NULL);
sleep(3); // 等待线程执行
pthread_cancel(tid); // 取消线程
pthread_join(tid, &ret);
if (ret == PTHREAD_CANCELED)
printf("线程被取消,返回 PTHREAD_CANCELED\n");
else
printf("线程正常结束\n");
}
运行结果:
线程正在运行...
线程正在运行...
线程正在运行...
线程被取消,返回 PTHREAD_CANCELED
🔑 总结
- return:线程函数结束,线程终止,可返回结果。
- pthread_exit():线程立即终止,返回值可传给
pthread_join
,不会影响进程其他线程。 - pthread_cancel():由其他线程取消线程,
pthread_join
返回PTHREAD_CANCELED
,线程未完成正常逻辑。 - 禁止使用 exit() 终止线程:
exit()
会结束整个进程,包括主线程。
6. 线程等待
线程等待主要使用 pthread_join
:
int pthread_join(pthread_t thread, void **value_ptr);
thread
:等待的线程 ID。value_ptr
:线程退出时返回的指针。- 如果线程通过
return
或pthread_exit
终止,value_ptr
保存线程返回值。 - 如果线程被
pthread_cancel
,返回PTHREAD_CANCELED
。 - 如果不关心返回值,可传
NULL
。
等待线程结束是必须的,否则线程退出的资源无法释放,造成系统泄漏。
示例(同时创建多个线程,并且一个一个等待)
void *routine(void *buf)
{
std::string str = static_cast<char *>(buf);
int cnt = 5;
while (cnt--)
{
std::cout << "我是子线程:" << str << std::endl;
}
return (void *)100;
}
int main()
{
std::vector<pthread_t> tids;
for (int i = 0; i < 5; i++)
{
pthread_t tid;
char *name = new char[64];
snprintf(name, 64, "thread-%d", i + 1);
int n = pthread_create(&tid, nullptr, routine, (void *)name);
if (n != 0)
{
std::cout << "pthread_create error: " << n << std::endl;
return 1;
}
tids.push_back(tid);
}
for (int i = 0; i < 5; i++)
{
int n = pthread_join(tids[i], nullptr);
if (n == 0)
{
std::cout << "线程等待成功" << std::endl;
}
else
{
std::cout << "线程等待失败" << std::endl;
}
}
return 0;
}
7. 分离线程
默认线程是 joinable 的,需要 pthread_join
回收资源。如果不关心返回值,可以分离线程:
pthread_detach(pthread_self());
分离后线程退出会自动释放资源,不能再用 pthread_join
。注意 joinable 与 detached 是互斥的。
示例:
void* thread_run(void* arg) {
pthread_detach(pthread_self());
printf("%s\n", (char*)arg);
return NULL;
}
int main() {
pthread_t tid;
if (pthread_create(&tid, NULL, thread_run, "thread run...") != 0) {
printf("create thread error\n");
return 1;
}
sleep(1); // 给线程执行时间
}
8. 线程 ID 与进程地址空间
pthread_create
会生成一个线程 ID,存放在pthread_t
类型变量中。- 这个 ID 是 NPTL 线程库内部维护的虚拟地址。
- 线程在内存中是轻量级进程,主线程和子线程栈地址不同,但共享进程的虚拟空间。
pthread_self
返回当前线程的 ID,用于线程间操作。
9. 总结与注意事项
- 线程参数:传递给线程函数的参数必须是堆上分配或全局变量,不能是局部变量。
- 线程返回值:通过
pthread_exit
或return
返回,内存要在堆上或全局。 - 线程等待:默认 joinable,需要
pthread_join
回收资源。 - 线程分离:如果不关心返回值,可用
pthread_detach
自动释放资源。 - 错误处理:pthread 函数出错通过返回值,不会设置全局
errno
。 - 线程 ID:
pthread_t
是库内部标识,进程内唯一,但不等同于内核线程 ID。