一、核心结论
线程终止后需通过pthread_join(阻塞回收)或pthread_detach(自动回收)释放资源,避免僵尸线程。线程属性(如栈大小、分离状态)可通过pthread_attr_t设置,灵活适配不同场景需求。
二、线程等待:pthread_join 函数详解
1. 为什么需要线程等待?
- 已退出的线程会残留退出状态和少量资源(如线程 ID),若不回收会成为 "僵尸线程",占用系统资源;
- 主线程需通过等待获取线程的退出状态(返回值、是否被取消);
- 避免主线程提前退出,导致子线程被强制终止。
2. 函数原型
int pthread_join(pthread_t thread, void **retval);
3. 参数说明
thread:目标线程的 ID;retval:输出参数,存储线程的退出状态:- 线程通过
return或pthread_exit终止:*retval为返回值; - 线程被
pthread_cancel取消:*retval为PTHREAD_CANCELED; - 不关心退出状态可传 NULL。
- 线程通过
4. 代码示例:获取不同终止方式的退出状态
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
// 方式1:return终止
void* thread_return(void* arg) {
printf("线程1:通过return终止\n");
int* res = (int*)malloc(sizeof(int));
*res = 10;
return (void*)res;
}
// 方式2:pthread_exit终止
void* thread_exit(void* arg) {
printf("线程2:通过pthread_exit终止\n");
int* res = (int*)malloc(sizeof(int));
*res = 20;
pthread_exit((void*)res);
}
// 方式3:被cancel终止
void* thread_cancel(void* arg) {
printf("线程3:循环执行,等待被取消\n");
while (1) {
sleep(1);
}
return NULL;
}
int main() {
pthread_t tid1, tid2, tid3;
void* exit_status;
int ret;
// 线程1:return终止
pthread_create(&tid1, NULL, thread_return, NULL);
ret = pthread_join(tid1, &exit_status);
if (ret == 0) {
printf("回收线程1,退出状态:%d\n", *(int*)exit_status);
free(exit_status); // 释放线程分配的内存
}
// 线程2:pthread_exit终止
pthread_create(&tid2, NULL, thread_exit, NULL);
ret = pthread_join(tid2, &exit_status);
if (ret == 0) {
printf("回收线程2,退出状态:%d\n", *(int*)exit_status);
free(exit_status);
}
// 线程3:被cancel终止
pthread_create(&tid3, NULL, thread_cancel, NULL);
sleep(3); // 让线程运行3秒
pthread_cancel(tid3);
ret = pthread_join(tid3, &exit_status);
if (ret == 0) {
if (exit_status == PTHREAD_CANCELED) {
printf("回收线程3,退出状态:PTHREAD_CANCELED\n");
}
}
return 0;
}
5. 运行结果
线程1:通过return终止
回收线程1,退出状态:10
线程2:通过pthread_exit终止
回收线程2,退出状态:20
线程3:循环执行,等待被取消
回收线程3,退出状态:PTHREAD_CANCELED
关键注意点:
pthread_join是阻塞函数,调用线程会暂停直到目标线程终止;- 线程的返回值若指向局部变量,会因栈销毁导致野指针,需用
malloc分配或使用全局变量; - 不能等待已分离的线程,否则返回 EINVAL 错误。
三、线程分离:pthread_detach 函数详解
1. 适用场景
若不关心线程的退出状态,pthread_join的阻塞会成为负担,此时可将线程设置为 "分离状态",线程终止后自动释放资源,无需手动等待。
2. 函数原型
int pthread_detach(pthread_t thread);
3. 两种分离方式
(1)其他线程为目标线程分离
pthread_t tid;
pthread_create(&tid, NULL, thread_func, NULL);
pthread_detach(tid); // 主线程为子线程分离
(2)线程自分离(推荐)
void* thread_func(void* arg) {
pthread_detach(pthread_self()); // 线程自身分离
printf("分离线程执行中\n");
sleep(2);
return NULL;
}
4. 代码示例:分离线程的使用
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <string.h>
void* thread_func(void* arg) {
pthread_detach(pthread_self()); // 自分离
printf("分离线程启动,ID:%lu\n", (unsigned long)pthread_self());
sleep(2);
printf("分离线程执行完毕,将自动回收资源\n");
return NULL;
}
int main() {
pthread_t tid;
int ret = pthread_create(&tid, NULL, thread_func, NULL);
if (ret != 0) {
printf("创建线程失败:%s\n", strerror(ret));
return -1;
}
sleep(1); // 等待线程完成分离(避免提前调用join)
// 尝试等待分离线程,会返回错误
ret = pthread_join(tid, NULL);
if (ret != 0) {
printf("等待分离线程失败:%s\n", strerror(ret));
}
sleep(2); // 等待线程执行完毕
printf("主线程退出\n");
return 0;
}
5. 运行结果
分离线程启动,ID:140709316464384
等待分离线程失败:Invalid argument
分离线程执行完毕,将自动回收资源
主线程退出
核心区别:joinable vs 分离状态
| 特性 | joinable(默认) | 分离状态 |
|---|---|---|
| 资源回收 | 需调用 pthread_join | 自动回收 |
| 阻塞性 | 调用线程阻塞 | 无阻塞 |
| 获取退出状态 | 可以 | 不可以 |
| 适用场景 | 需获取线程执行结果 | 后台服务线程 |
四、线程属性:pthread_attr_t 详解
线程属性用于定制线程的行为(如栈大小、调度优先级),需先初始化属性对象,设置属性后传入pthread_create。
1. 核心属性操作流程
- 初始化属性:
pthread_attr_init(&attr); - 设置属性(如栈大小、分离状态);
- 创建线程时传入属性:
pthread_create(&tid, &attr, func, arg); - 销毁属性:
pthread_attr_destroy(&attr)。
2. 常用属性设置
(1)设置线程分离状态
pthread_attr_t attr;
pthread_attr_init(&attr);
// 设置为分离状态(创建后无需join)
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, thread_func, NULL);
pthread_attr_destroy(&attr);
(2)设置栈大小
size_t stack_size = 1024 * 1024; // 1MB
pthread_attr_setstacksize(&attr, stack_size);
3. 代码示例:使用线程属性创建分离线程
c
运行
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
void* thread_func(void* arg) {
printf("属性线程启动,ID:%lu\n", (unsigned long)pthread_self());
sleep(2);
printf("属性线程执行完毕\n");
return NULL;
}
int main() {
pthread_t tid;
pthread_attr_t attr;
int ret;
// 初始化属性
ret = pthread_attr_init(&attr);
if (ret != 0) {
printf("初始化属性失败:%s\n", strerror(ret));
return -1;
}
// 设置为分离状态
ret = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
if (ret != 0) {
printf("设置分离状态失败:%s\n", strerror(ret));
return -1;
}
// 创建线程(传入属性)
ret = pthread_create(&tid, &attr, thread_func, NULL);
if (ret != 0) {
printf("创建线程失败:%s\n", strerror(ret));
return -1;
}
// 销毁属性(创建线程后即可销毁)
pthread_attr_destroy(&attr);
sleep(3); // 等待线程执行完毕
printf("主线程退出\n");
return 0;
}
五、总结
- 线程等待用
pthread_join,可获取退出状态并释放资源,适用于需同步结果的场景; - 线程分离用
pthread_detach,自动回收资源,适用于无需关注结果的后台线程; - 线程属性可定制线程行为,核心是 "初始化→设置→使用→销毁" 的流程;
- 新手需重点掌握 "joinable vs 分离" 的区别,避免僵尸线程或无效等待。
下一篇将讲解线程 ID 与进程地址空间布局,深入理解线程的内存分布,敬请关注!
下面补一份贯穿始终的代码
cpp
#ifndef _THREAD_H_
#define _THREAD_H_
#include <iostream>
#include <cstdio>
#include <string>
#include <pthread.h>
#include <cstring>
#include <cstdint>
#include <functional>
static uint32_t number = 1;
template<class T>
class Thread
{
public:
using func_t = std::function<void(T)>;
Thread(func_t func, T date)
: _tid(0)
, _isdetach(false)
, _isrunning(false)
, res(nullptr)
, _func(func)
, _date(date)
{
_name = "pthread -> " + std::to_string(number++);
}
~Thread()
{
}
void EnableDetach() // 启动前设置分离
{
_isdetach = true;
}
void Detach() // 启动后设置分离
{
if (_isdetach || !_isrunning)
return;
pthread_detach(_tid);
EnableDetach();
}
void EnableRunning()
{
_isrunning = true;
}
static void *Routine(void *args)
{
((Thread<T>*)args)->EnableRunning();
if (((Thread<T>*)args)->_isdetach)
pthread_detach(((Thread<T>*)args)->_tid);
((Thread<T>*)args)->_func(((Thread<T>*)args)->_date);
return nullptr;
}
bool Start()
{
if (_isrunning)
return false;
int n = pthread_create(&_tid, nullptr, Routine, this);
if (n != 0)
{
std::cout << "pthread_create fail : " << strerror(n) << std::endl;
return false;
}
else
{
std::cout << "pthread_create success, name : " << _name << std::endl;
return true;
}
}
bool Stop() // cancel
{
if (_isrunning)
{
int n = pthread_cancel(_tid);
if (n != 0)
{
std::cout << "stop thread error : " << strerror(n) << std::endl;
return false;
}
else
{
std::cout << "stop thread success!" << std::endl;
_isrunning = false;
return true;
}
}
return false;
}
void Join()
{
if (_isdetach)
{
std::cout << "thread is detach!" << std::endl;
return;
}
int n = pthread_join(_tid, &res);
if (n != 0)
std::cout << "join thread error : " << strerror(n) << std::endl;
else
{
std::cout << "join thread success!" << std::endl;
_isrunning = false;
}
}
private:
pthread_t _tid;
std::string _name;
bool _isdetach;
bool _isrunning;
void *res;
func_t _func;
T _date;
};
#endif
cpp
#include "Thread.hpp"
#include <unistd.h>
class game
{
public:
void start()
{
std::cout << "请输入你的目标值 -> ";
std::cin >> _target;
_target %= 100;
int count = 1;
while (true)
{
int n1 = 0;
int n2 = 0;
n1 = rand() % 100;
n2 = rand() % 100;
if (n1 + n2 == _target)
{
std::cout << "猜对啦!" << std::endl;
break;
}
else
{
printf("第 %d 次错误, 错误值为 : %d\n", count++, n1 + n2);
}
//sleep(1);
}
}
private:
int _target;
};
void test(game g)
{
g.start();
}
int main()
{
game g;
Thread<game> t(test, g);
t.Start();
t.Join();
return 0;
}
/*
void func(int cnt)
{
while (cnt--)
{
std::cout << "我是一个新线程!" << std::endl;
sleep(1);
}
}
int main()
{
int cnt = 10;
//Thread<int> thread(func, cnt);
Thread<int> thread([](int cnt){
while (cnt--)
{
std::cout << "我是一个新线程!" << std::endl;
sleep(1);
}
}, cnt);
thread.Start();
thread.Join();
return 0;
}
*/