当前位置: 首页 > news >正文

【系统编程】线程控制原语

文章目录

  • 一、获取线程 id
    • 1.1 线程号 `pthread_t`
    • 1.2 函数 `pthread_self`
    • 1.3 函数 `pthread_equal`
  • 二、创建线程
    • 2.1 创建子线程
    • 2.2 循环创建N个子线程
    • 2.3 子线程传参地址错误
  • 三、线程退出
    • 3.1 线程退出方式比较
    • 3.2 函数 `pthread_exit()`
  • 四、线程资源回收
  • 五、线程分离
    • 5.1 线程结束与资源回收
    • 5.2 函数`pthread_detach`
  • 六、杀死(取消)线程
  • 七、线程进程控制原语对比

一、获取线程 id

1.1 线程号 pthread_t

  • 类型:pthread_t
  • 范围:只在所属进程中唯一
  • 底层实现:
    • Linux下一般是无符号长整数
    • 有些系统可能是结构体,不能强行转换为整数来比较

1.2 函数 pthread_self

#include <pthread.h>
pthread_t pthread_self(void);
  • 作用:获取线程 id,在进程内部去标识线程身份。
  • 返回值:线程ID(类型为 pthread_t

1.3 函数 pthread_equal

int pthread_equal(pthread_t t1, pthread_t t2);
  • 作用:判断 t1t2 是否是同一个线程
  • 返回值
    • 相同:返回非 0
    • 不同:返回 0
  • 不能直接用 == 比较,为了可移植性必须使用该函数

二、创建线程

2.1 创建子线程

#include <pthread.h>int pthread_create(pthread_t *thread,const pthread_attr_t *attr,void *(*start_routine)(void *),void *arg
);
  • 参数:

    • thread:传出参数,新子线程的线程 id
    • attr:线程属性,默认传 NULL,表示使用默认属性
    • start_routine函数指针,子线程的入口函数地址(回调函数)
      • 指向线程主函数(线程体),该函数运行结束,则线程结束。
      • pthread_create 调用成功,该函数会被自动调用起来
    • arg:线程主体函数执行期间所使用的函数参数。(void* 类型)
  • 返回值:

    • 成功:0
    • 失败:返回错误码(不是设置到 errno
  • 错误处理:

    • pthread_create 的错误码不保存在 errno 中
    • 不能使用 perror() 打印错误信息
    • 应该使用 strerror(ret) 将错误码转为字符串再打印
    • 示例:
    #include <string.h>
    char *strerror(int errnum);
    // 举例应用:
    int ret = pthread_create(&tid, NULL, thread_fun, (void *)&test);
    if (ret != 0) {printf("error number: %d\n", ret);fprintf(stderr, "error information: %s\n", strerror(ret));
    }
    
  • 创建线程示例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>// 子线程的主体函数
void *tfn(void *arg)
{printf("tfn: pid = %d, pthread_id = %lu\n", getpid(), pthread_self());return NULL;
}int main(int argc, char *argv[])
{pthread_t tid;// 创建子线程int ret = pthread_create(&tid, NULL, tfn, NULL);if (ret != 0) {fprintf(stderr, "pthread_create err: %s\n", strerror(ret));}printf("main: pid = %d, pthread_id = %lu\n", getpid(), pthread_self());sleep(1);  // 给子线程执行时间return 0;  // 释放进程地址空间
}

2.2 循环创建N个子线程

  • 每个子线程打印自己是第几个被创建出来的

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>void sys_err(const char *str)
{perror(str);exit(1);
}void *tfn(void *arg)
{int i = (int)arg;printf("this is %dth thread, pid: %d, tid: %lu\n", i+1, getpid(), pthread_self());return NULL;
}int main(int argc, char *argv[])
{int n = 5;if (argc == 2) {n = atoi(argv[1]);}int i, ret;pthread_t tid;for (i = 0; i < n; i++) {ret = pthread_create(&tid, NULL, tfn, (void *)i);if (ret != 0) {fprintf(stderr, "pthread_create err: %s\n", strerror(ret));}}usleep(1000);printf("this is main thread, pid: %d, tid: %lu\n", getpid(), pthread_self());return 0;
}

2.3 子线程传参地址错误

for (i = 0; i < n; i++) {ret = pthread_create(&tid, NULL, tfn, (void *)&i);if (ret != 0) {fprintf(stderr, "pthread_create err: %s\n", strerror(ret));}
}
void *tfn(void *arg)
{int i = *(int*)arg;printf("this is %dth thread, pid: %d, tid: %lu\n", i+1, getpid(), pthread_self());return NULL;
}
  • void *argint i = *(int*)arg;
    • void *通用指针,可以指向任意类型(但不能直接解引用)。
    • (int *)arg类型强制转换,把 void * 转成 int *
    • *(int *)arg 是取出这个地址指向的 int

在这里插入图片描述

错误原因:

多个线程共享同一个变量的地址,从而造成数据竞争或混乱

因为 &i 是主线程中的局部变量地址。多个线程在启动时都接收到这个相同的地址,而主线程在循环中每次 i++这个值在原地址上被修改,线程看到的是不断变化的结果。

  • 线程启动是异步的,多个线程可能还没来得及执行,主线程已经把 i 改了。
  • 所以每个线程读取的 i 值都不一定是自己那一轮的值,而是最后或中间某次循环的值。

正确:

方法 1:定义独立变量(如堆上申请)

for (int i = 0; i < 5; i++) {int *arg = malloc(sizeof(int));*arg = i;pthread_create(&tid[i], NULL, tfn, arg);
}

并在线程中释放:

void *tfn(void *arg)
{int i = *(int*)arg;printf("this is %dth thread\n", i+1);free(arg);  // 避免内存泄漏return NULL;
}

方法 2:使用数组,保证每次传入的地址是独立的

int args[5];
for (int i = 0; i < 5; i++) {args[i] = i;pthread_create(&tid[i], NULL, tfn, (void *)&args[i]);
}

这种方式要确保 args 数组的生命周期足够长,在所有线程结束前不能销毁。

三、线程退出

3.1 线程退出方式比较

方式作用对象效果建议使用场景
return当前线程返回到调用者那里简单的线程函数使用
pthread_exit当前线程正常退出当前线程推荐,线程安全退出
exit当前进程终止整个进程(所有线程终止)不要在线程中使用
  • exit() 终止整个进程,不仅仅是当前线程。会导致所有线程立刻终止。
  • pthread_cancel() 可以杀死线程,但需要“保存点” —— 进内核即可得到。

3.2 函数 pthread_exit()

#include <pthread.h>
void pthread_exit(void *retval);
  • 参数:返回给 pthread_join() 的退出值(可以是 NULL,也可以是指针)
  • 如果希望线程返回一个值,让别的线程(如主线程)获取它,就传一个指针进去
    • 该指针不能指向线程函数内部的局部变量(栈内存)
    • 当线程退出后,栈帧销毁,指针失效。
    • 主线程通过 pthread_join 获取到的是“野指针”。

四、线程资源回收

  • 阻塞等待回收子线程
#include <pthread.h>
// int pthread_create(pthread_t *thread,...) 这里参1是指针
int pthread_join(pthread_t thread, void **retval);
  • 参数:

    • pthread_t:待回收的线程 id
    • retval:传出参数,保存待回收线程返回值的指针地址(void **
  • 返回值:

    • 成功:0
    • 失败:错误号
  • 示例:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>struct thrd {int var;char str[256];
};// 子线程的主体函数
void *tfn(void *arg)
{// struct thrd th;// th.var = 100;// strcpy(th.str, "hello thread")// pthread_exit((void *)&th);// 定义的局部变量,函数调用结束会被释放掉struct thrd *tval = (struct thrd *)arg;tval->var = 100;strcpy(tval->str, "hello thread");pthread_exit((void *)tval);// return (void *)tval;  // 也可以
}int main(int argc, char *argv[])
{pthread_t tid;struct thrd arg, *retval;// 创建子线程int ret = pthread_create(&tid, NULL, tfn, (void *)&arg);if (ret != 0) {fprintf(stderr, "pthread_create err: %s\n", strerror(ret));}// 回收子线程退出值ret = pthread_join(tid, (void**)&retval);if (ret != 0) {fprintf(stderr, "pthread_join err: %s\n", strerror(ret));}printf("child thread exit with: var = %d, str = %s\n", retval->var, retval->str);  // void* 指针是8字节pthread_exit((void *)0);  // 退出主线程return 0;  // 释放进程地址空间
}

五、线程分离

5.1 线程结束与资源回收

  • 线程结束后,终止状态会一直保留,直到其他线程用 pthread_join 获取它的状态并回收资源。
  • 如果没有 pthread_join,资源不会释放,会产生“僵尸线程”现象(类似僵尸进程)。

5.2 函数pthread_detach

int pthread_detach(pthread_t thread);
  • 参数 thread:待设置为分离的线程 id
  • 作用:
    • 把线程设置为“分离状态”。
    • 分离状态的线程结束后,系统自动回收资源,不保留终止状态。
    • 分离后不能再用 pthread_join,否则返回 EINVAL 错误。
    • 已经分离的线程,不能再用 pthread_join,不能获取线程退出值。
  • 返回值:
    • 成功:0
    • 失败:错误号
  • 特点:
    • 调用后不阻塞。
    • 常用于网络、多线程服务器,避免手动回收。
    • 也可以在 pthread_create 时通过线程属性设置为分离状态。

六、杀死(取消)线程

int pthread_cancel(pthread_t thread);
  • 参数:待杀死的线程 id
  • 作用:
    • 请求取消指定线程(类似进程的 kill,但不是立即终止)。
  • 返回值:
    • 成功:0
    • 失败:错误号
  • 注意事项
    • 线程取消是延时的,需要线程运行到“取消点(保存点)”才能真正生效。
    • 取消点(保存点):线程检查是否被取消的地方,通常是一些系统调用(如 readwriteopenclose 等)。
    • 如果线程没有取消点,可在代码中调用 pthread_testcancel() 手动设置一个取消点。
  • 被取消线程的退出值
    • 宏定义:#define PTHREAD_CANCELED ((void *) -1)
    • 被取消后,用 pthread_join 获取的退出值是 (void *) -1

七、线程进程控制原语对比

功能进程原语线程原语说明
创建forkpthread_create进程创建后资源独立,线程创建共享进程资源
退出exitpthread_exit结束当前执行单元并释放资源
回收(等待结束)wait/waitpid()pthread_join等待子进程/线程结束并回收资源
终止(强制结束)killpthread_cancel发送信号或请求终止执行
获取 IDgetpidpthread_self获取当前进程/线程 ID
返回 IDgetppid(父进程ID)无直接对应(需保存)线程父子关系不严格,对应的是同属一个进程
分离无直接对应pthread_detach()线程设置为“分离状态”,分离状态的线程结束后,系统自动回收资源
http://www.dtcms.com/a/351306.html

相关文章:

  • 半小时打造七夕传统文化网站:Qoder AI编程实战记录
  • Ansible配置文件
  • 2025第五届人工智能、自动化与高性能计算国际会议 (AIAHPC 2025)
  • YUM配置
  • 适配欧拉操作系统
  • 高频面试题:说一下线程池吧?(线程池原理,核心参数,创建方式,应用场景都要说到才能让面试官心服口服)
  • 什么是AQS?
  • Xposed框架实战指南:从原理到你的第一个模块
  • R语言使用随机森林对数据进行插补
  • 【Java基础】Java数据结构深度解析:Array、ArrayList与LinkedList的对比与实践
  • 【HarmonyOS NEXT】打包鸿蒙应用并发布到应用市场
  • 构建生产级 RAG 系统:从数据处理到智能体(Agent)的全流程深度解析
  • Linux 网络数据收发全栈工具书:从 nc、socat 到 iperf3 的 Buildroot 路径与跨平台实战
  • 开心实习之第三十二天
  • Python爬虫实战:Uiautomator2 详解与应用场景
  • Android SystemServer 系列专题【篇四:SystemServerInitThreadPool线程池管理】
  • android 事件分发源码分析
  • STL库——vector(类函数学习)
  • 【51单片机】萌新持续学习中《矩阵 密码锁 点阵屏》
  • 矩阵初等变换的几何含义
  • 血缘元数据采集开放标准:OpenLineage Integrations Apache Spark Configuration Usage
  • 重写BeanFactory初始化方法并行加载Bean
  • 信息网络安全视角下的在线问卷调查系统设计与实践(国内问卷调查)
  • 记一个Mudbus TCP 帮助类
  • Linux 内核 Workqueue 原理与实现及其在 KFD SVM功能的应用
  • LeetCode - 844. 比较含退格的字符串
  • LeetCode 438. 找到字符串中所有的字母异位词
  • 微算法科技(NASDAQ:MLGO)通过修改 Grover 算法在可重构硬件上实现动态多模式搜索
  • LeetCode - 946. 验证栈序列
  • 智慧园区:从技术赋能到价值重构,解锁园区运营新范式