线程清理机制(pthread_cleanup函数族实践)
#include <unistd.h> // 提供 sleep() 函数
#include <pthread.h> // 多线程编程
#include <stdio.h> // 标准输入输出
#include <string.h> // 字符串处理
#include <stdlib.h> // 内存分配#define PI 3.1415 // 圆周率常量// 第一个清理处理函数
void clean(void* argp)
{puts("异常终止,清理函数被执行!");if(argp)free(argp); // 释放动态分配的内存
}// 第二个清理处理函数(被注释掉了)
void clear(void* argp)
{puts("异常终止,清理函数2被执行!");
}// 线程1的执行函数:计算圆的面积
void* routine(void* argp)
{int* p = (int*)malloc(sizeof(int)); // 分配内存存储结果// 注册清理处理函数(压入清理栈)// 参数:清理函数指针,传递给清理函数的参数pthread_cleanup_push(clean, p);// pthread_cleanup_push(clear,p); // 第二个清理函数被注释for(int i = 1; ; i++){printf("开始圆的面积计算(%d)\n",i);if(PI*i*i > 150.0) // 当面积超过150时退出循环{*p = i; // 存储结果break; // 正常退出循环} sleep(1); // 休眠1秒(取消点)}// 弹出清理处理函数,参数0表示不执行清理函数pthread_cleanup_pop(0);// pthread_cleanup_pop(0); // 第二个弹出被注释pthread_exit(p); // 正常退出线程,返回结果return NULL; // 这行不会执行到
}// 线程2的执行函数:负责取消线程1
void* routine2(void* argp)
{pthread_t id = *((pthread_t*)argp); // 获取线程1的IDprintf("1秒后向线程(%lu)发送退出请求\n",id);sleep(10); // 等待10秒(注意:这里改成了10秒!)pthread_cancel(id); // 发送取消请求return NULL;
}int main(int argc,char** argv)
{pthread_t id, id2; // 线程ID// 创建线程1(计算线程)int error = pthread_create(&id, NULL, routine, NULL);if(error){puts(strerror(error));return -1;}// 创建线程2(取消线程)error = pthread_create(&id2, NULL, routine2, &id);if(error){puts(strerror(error));return -1;}int *pret = NULL; // 用于接收线程1的返回值// 等待线程1结束if(error = pthread_join(id, (void**)&pret)){printf("Join:%s\n",strerror(error));return -1;}puts("线程1已经结束");// 检查线程1的退出状态if(pret && pret != PTHREAD_CANCELED){printf("子线程1退出值:%d\n",*pret); // 输出半径值printf("子线程正常退出,主线程回收资源\n"); // 提示信息free(pret); // 释放内存}// 等待线程2结束pthread_join(id2, NULL);puts("线程2已经结束");return 0;
}
关键机制详解
1. pthread_cleanup_push
和 pthread_cleanup_pop
pthread_cleanup_push(clean, p); // 注册清理函数
// ... 线程代码 ...
pthread_cleanup_pop(0); // 移除清理函数,不执行
清理函数执行时机:
线程被
pthread_cancel()
取消时线程调用
pthread_exit()
退出时线程调用
pthread_cleanup_pop(1)
时
2. pthread_cleanup_pop
参数含义
pthread_cleanup_pop(0); // 移除清理函数但不执行
pthread_cleanup_pop(1); // 移除清理函数并执行
3. 程序执行逻辑分析
情况1:计算先完成(大概率)
线程1在约7秒内完成计算(i=7时面积153.94>150)
线程2等待10秒后才发送取消请求
线程1正常退出,清理函数不会执行
主线程正常回收资源
执行流程:
开始圆的面积计算(1) 1秒后向线程(12345678)发送退出请求 开始圆的面积计算(2) ... 开始圆的面积计算(7) ← 正常完成计算 线程1已经结束 子线程1退出值:7 子线程正常退出,主线程回收资源 线程2已经结束
情况2:被取消(小概率,如果计算很慢)
线程1计算超过10秒
线程2发送取消请求
线程1在
sleep(1)
取消点响应清理函数会执行
内存管理分析
正常退出路径:
break; // 退出循环
pthread_cleanup_pop(0); // 不执行清理函数
pthread_exit(p); // 返回指针p
// 主线程中:
free(pret); // 主线程释放内存
被取消路径:
// 在sleep(1)时被取消
clean(p); // 清理函数执行,free(p)
// 主线程中:
pret == PTHREAD_CANCELED // 不会进入free(pret)分支
潜在问题
竞争条件:线程2等待10秒,线程1约7秒完成,所以线程1几乎总是正常完成
内存双重释放:如果清理函数执行了
free(p)
,而主线程又执行free(pret)
,会导致崩溃sleep(10)过长:线程2等待10秒,而线程1只需要约7秒,取消很少发生
改进建议
// 修改线程2的等待时间
sleep(3); // 改为3秒,增加取消的概率// 或者在主线程中完善取消情况的处理
if(pret == PTHREAD_CANCELED) {printf("线程1被取消\n");
} else if(pret != NULL) {printf("子线程1正常退出,返回值:%d\n",*pret);free(pret);
}