Linux----线程

一、基础概念对比
| 特性 | 进程 (Process) | 线程 (Thread) |
|---|---|---|
| 资源分配 | 资源分配的基本单位(独立地址空间) | 共享进程资源 |
| 调度单位 | 操作系统调度单位 | CPU调度的最小单位 |
| 创建开销 | 高(需复制父进程资源) | 低(共享进程资源) |
| 通信方式 | 管道、共享内存、消息队列等IPC | 共享全局变量(需同步机制) |
| 隔离性 | 内存隔离,安全性高 | 共享内存,需处理竞争条件 |
| 典型组成 | 代码段+数据段+堆栈段+PCB | 线程ID+寄存器组+栈+线程控制块TCB |
二、线程组成详解
1. 核心组件
struct thread_struct {pthread_t tid; // 线程ID (8字节)void* stack_base; // 栈基地址 (8字节)size_t stack_size; // 栈大小 (Linux默认8MB)void* (*start_routine)(void*); // 入口函数指针void* arg; // 入口函数参数// 寄存器组保存区 (约52个寄存器,约416字节)// 包括:PC、SP、通用寄存器、浮点寄存器等
};
2. 关键特征
- 线程ID:
pthread_t类型,进程内唯一 - 独立栈空间:每个线程拥有独立调用栈
- 共享资源:全局变量、堆内存、文件描述符等
三、线程创建与管理
1. 创建函数原型
#include <pthread.h>
int pthread_create(pthread_t *thread, const pthread_attr_t *attr,void *(*start_routine)(void *), void *arg);
参数详解表
| 参数 | 类型 | 作用说明 |
|---|---|---|
thread | pthread_t* | 输出参数,存储新线程ID |
attr | pthread_attr_t* | 线程属性(NULL使用默认属性):<br>▪ 栈大小<br>▪ 调度策略<br>▪ 分离状态 |
start_routine | void* (*)(void*) | 线程入口函数(返回值为线程退出状态) |
arg | void* | 传递给入口函数的参数 |
返回值
- 成功返回
0 - 失败返回错误码(非
errno值,需用strerror转换) 
2. 编译指令
gcc program.c -lpthread -o program # 必须链接pthread库
3. 线程终止方式
/* 主动退出(带返回值)*/
void pthread_exit(void *retval);/* 被动终止(被其他线程取消)*/
int pthread_cancel(pthread_t thread);
注意事项
retval必须指向堆/静态存储区,不能是线程栈内存- 主线程退出会导致进程终止(即使其他线程仍在运行)
四、线程终止方式详解(补充)
根据POSIX标准,线程可通过以下四种方式终止执行:
1. 显式调用退出函数
void* worker(void* arg) {// 动态分配返回值int* result = malloc(sizeof(int));*result = 100;// 显式退出并传递状态值pthread_exit((void*)result); // 正确:堆内存// pthread_exit(&local_var); // 危险!栈内存会被回收
}
特点:
- 退出状态值通过
pthread_join()获取 - 必须保证返回值内存有效性(推荐使用堆内存或全局变量)
2. 从入口函数返回
void* worker(void* arg) {static int result = 200; // 静态存储期变量return (void*)&result; // 等效于 pthread_exit()
}
注意:
- 返回值类型必须为
void* - 禁止返回局部变量地址(函数退出后栈空间失效)
3. 被其他线程取消
// 请求取消目标线程
pthread_cancel(tid);// 目标线程中设置取消点
void* worker(void* arg) {while(1) {pthread_testcancel(); // 手动设置取消点// 长时间工作...}return NULL;
}
关键机制:
| 取消类型 | 行为特征 | 设置函数 |
|---|---|---|
PTHREAD_CANCEL_DEFERRED(默认) | 在下一个取消点终止 | pthread_setcanceltype() |
PTHREAD_CANCEL_ASYNCHRONOUS | 立即终止(可能破坏数据一致性) | pthread_setcanceltype() |
4. 进程级终止
void* thread_func(void* arg) {sleep(1);printf("此消息不会被打印\n");return NULL;
}int main() {pthread_t tid;pthread_create(&tid, NULL, thread_func, NULL);// 错误示范:主线程直接返回// return 0; // 导致所有线程立即终止// 正确做法:主线程等待子线程pthread_exit(NULL); // 仅退出主线程,不影响其他线程
}
重要规则:
exit()会终止整个进程及其所有线程- 主线程
return会隐式调用exit() - 建议主线程使用
pthread_exit()代替return
五、线程状态回收机制
1. 等待线程终止
void* status;
int ret = pthread_join(tid, &status);if (ret == 0) {printf("线程退出码:%d\n", *(int*)status);free(status); // 清理堆内存
} else {perror("等待线程失败");
}
2. 分离线程(自动回收)
// 创建时设置分离属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, worker, NULL);// 或运行时分离
pthread_detach(tid);
//注意在建立线程后就设置分离
特性:
- 分离线程终止后自动回收资源
- 无法使用
pthread_join()获取状态 - 适用于不需要返回值的后台任务
六、线程终止流程图解
graph TDA[线程开始] --> B{终止方式}B -->|pthread_exit| C[传递状态值]B -->|return| CB -->|pthread_cancel| D[清理处理程序]B -->|exit| E[终止所有线程]C --> F[状态值存储]D --> G[调用清理栈函数]F --> H[pthread_join获取]G --> I[资源释放]E --> J[进程终止]style C fill:#c9f,stroke:#333style D fill:#f96,stroke:#333style E fill:#f00,stroke:#333
七、最佳实践建议
-
资源管理三原则:
- 谁分配谁释放
- 退出前释放非共享资源
- 使用
pthread_cleanup_push()注册清理函数
-
取消安全设计:
void cleanup(void* arg) {printf("清理资源:%p\n", arg);free(arg); }void* worker(void* arg) {void* res = malloc(1024);pthread_cleanup_push(cleanup, res);// 可能被取消的代码区while(1) {pthread_testcancel();// 关键操作...}pthread_cleanup_pop(1); // 执行清理return NULL; } -
状态值传递规范:
- 简单状态码使用
int类型转换pthread_exit((void*)(intptr_t)error_code); - 复杂数据结构使用堆内存
struct Result* res = malloc(sizeof(struct Result)); /* 填充数据 */ pthread_exit(res);
- 简单状态码使用
4.练习
练习1:创建一个线程
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>void * do_something(void *arg)
{printf("do copy file---\n");return NULL;
}int main(int argc, const char *argv[])
{pthread_t tid;int ret;if((ret = pthread_create(&tid,NULL,do_something,NULL)) != 0){errno = ret;perror("pthread_create fail");return -1;}printf("-----main-------\n");sleep(1);return 0;return 0;
}
练习2:创建多个线程
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>void * do_one(void *arg)
{printf("pthread 1 pid = %d\n",getpid());return NULL;
}void * do_two(void *arg)
{printf("pthread 2 pid = %d\n",getpid());return NULL;
}void * do_three(void *arg)
{printf("pthread 3 pid = %d\n",getpid());return NULL;
}typedef void *(*thread_cb_t)(void*);int main(int argc, const char *argv[])
{printf("---main--- pid = %d\n",getpid());pthread_t tid[3];int ret;thread_cb_t func[3] = {do_one,do_two,do_three};int i = 0;for(i = 0;i < 3;i++){if((ret = pthread_create(&tid[i],NULL,func[i],NULL)) != 0){errno = ret;perror("pthread1_create fail");return -1;}}sleep(1);return 0;return 0;
}
练习3:线程的关闭
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>void * do_something(void *arg)
{static int ret = 100;printf("do copy file---\n");//pthread_exit("i am dead\n");pthread_exit(&ret);//return NULL;
}int main(int argc, const char *argv[])
{pthread_t tid;int ret;if((ret = pthread_create(&tid,NULL,do_something,NULL)) != 0){errno = ret;perror("pthread_create fail");return -1;}printf("-----main-------\n");int *retval;//char *retval;pthread_join(tid,(void **)&retval);//printf("*retval = %s\n",retval);printf("*retval = %d\n",*retval);sleep(1);return 0;return 0;
}

练习4:多线程拷贝文件(缺陷当文件过大,会导致偏移量出错)
#include <stdio.h>
#include <pthread.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/stat.h>
#include <unistd.h>typedef struct
{int fd_s;int fd_d;int size;int len;int id;
}msg_t;void * do_copy (void *arg)
{msg_t p = *(msg_t*)arg;lseek(p.fd_s,p.size*p.id,SEEK_SET);lseek(p.fd_d,p.size*p.id,SEEK_SET);// printf("tid = %ld id = %d fd_s = %d fd_d = %d size = %d len = %d\n",pthread_self(),p.id,p.fd_s,p.fd_d,p.size,p.len);//调试代码char buf[p.len];int ret = read(p.fd_s,buf,p.len);write(p.fd_d,buf,ret);return NULL;
}//cp src dest
int main(int argc, const char *argv[])
{if (argc!=3){printf("Usage: %s <src> <dest>\n",argv[0]);return -1;}int fd_s = open(argv[1],O_RDONLY);int fd_d = open(argv[2],O_WRONLY|O_TRUNC|O_CREAT,0666);if (fd_s < 0 || fd_d < 0){perror("open fail");return -1;}int n = 0;printf("Input threads num: ");scanf("%d",&n);int i = 0;int ret = 0;pthread_t tid[n];msg_t msg[n];struct stat st;if (stat(argv[1],&st) < 0){perror("stat fail");return -1;}int f_len = st.st_size;for (i = 0; i < n; ++i){msg[i].fd_s = fd_s;msg[i].fd_d = fd_d;msg[i].size = f_len / n;msg[i].id = i;#if 1if (i == n-1){ msg[i].len = f_len - (f_len/n)*(n-1);}else {msg[i].len = f_len/n;}
#endifret = pthread_create(&tid[i],NULL,do_copy,&msg[i]);if (ret != 0){errno = ret;perror("pthread_create fail");return -1;}}printf("----main-----\n");for (i = 0; i < n; ++i)pthread_join(tid[i],NULL);close(fd_s);close(fd_d);return 0;
}
八、线程生命周期管理
1. 线程属性设置(示例)
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); // 分离属性
pthread_attr_setstacksize(&attr, 2*1024*1024); // 设置2MB栈
2. 线程同步机制
| 机制 | 用途 | 相关函数 |
|---|---|---|
| 互斥锁 | 保护共享资源 | pthread_mutex_*系列 |
| 条件变量 | 线程间事件通知 | pthread_cond_*系列 |
| 读写锁 | 读写操作分离 | pthread_rwlock_*系列 |
| 信号量 | 控制并发访问数量 | sem_*系列 |
九、典型问题与解决方案
1. 资源竞争问题
场景:多个线程同时修改全局变量
解决:
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;void* counter_thread(void* arg) {for(int i=0; i<100000; ++i) {pthread_mutex_lock(&mutex);global_counter++;pthread_mutex_unlock(&mutex);}return NULL;
}
2. 僵尸线程问题
现象:已终止但未回收的线程占用系统资源
解决方案:
- 使用
pthread_join阻塞回收:void* retval; pthread_join(tid, &retval); // 类似进程的waitpid free(retval); // 清理返回值 - 或设置分离属性:
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
相关文章:
Linux----线程
一、基础概念对比 特性进程 (Process)线程 (Thread)资源分配资源分配的基本单位(独立地址空间)共享进程资源调度单位操作系统调度单位CPU调度的最小单位创建开销高(需复制父进程资源)低(共享进程资源)通信…...
实现rolabelimg对于dota格式文件的直接加载和保存
在本篇博客中,我们将讲解如何修改roLabelImg.py文件,使其能够直接加载和保存Dota格式的标注文件(txt)以替换掉复杂的xml文件。通过对源代码的修改,我们将实现支持加载并保存Dota格式标注数据,以便与roLabel…...
bboss v7.3.5来袭!新增异地灾备机制和Kerberos认证机制,助力企业数据安全
ETL & 流批一体化框架 bboss v7.3.5 发布,多源输出插件增加为特定输出插件设置记录过滤功能;Elasticsearch 客户端新增异地双中心灾备机制,提升框架高可用性;Elasticsearch client 和 http 微服务框架增加对 Kerberos 认证支持…...
华为昇腾服务器固件Firmware、驱动Drive、CANN各自的作用与联系?
文章目录 **1. 固件(Firmware)****2. 驱动(Driver)****3. CANN(Compute Architecture for Neural Networks)****三者关系****典型问题定位** 华为昇腾服务器的固件、驱动和CANN是支撑其AI计算能力的核心组件…...
MySQL 视图入门
一、什么是 MySQL 视图 1.1 视图的基本概念 在 MySQL 中,视图是一种虚拟表,它本身并不存储实际的数据,而是基于一个或多个真实表(基表)的查询结果集。可以把视图想象成是一个预定义好的查询语句的快捷方式。当你查询…...
算法很美笔记(Java)——动态规划
解重叠子问题(当前解用到了以前求过的解) 形式:记忆型递归或递推(dp) 动态规划本质是递推,核心是找到状态转移的方式,也就是填excel表时的逻辑(填的方式),而…...
C++ ——继承
体现的是代码复用的思想 1、子类继承父类,子类就拥有了父类的特性(成员方法和成员属性) 2、已存在的类被称为“基类”或者“父类”或者“超类”;新创建的类被称为“派生类”或者“子类” 注意: (1&#…...
LeetCode 热题 100 283. 移动零
LeetCode 热题 100 | 283. 移动零 大家好,今天我们来解决一道经典的算法题——移动零。这道题在LeetCode上被标记为简单难度,要求我们将数组中的所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。下面我将详细讲解解题思路,…...
游戏引擎学习第116天
回顾昨天的工作 本次工作内容主要集中在游戏开发的低级编程优化,尤其是手动优化软件渲染。工作目的之一是鼓励开发者避免依赖外部库,而是深入理解代码并进行优化。当前阶段正进行SIMD(单指令多数据)优化,使用Intel推荐…...
react(9)-redux
使用CRA快速创建react项目 npx create-react-app react-redux 安装配套工具 npm i reduxjs/toolkit react-redux 启动项目 在创建项目时候会出现一个问题 You are running create-react-app 5.0.0, which is behind the latest release (5.0.1). We no longer support…...
Linux内核实时机制7 - 实时改造机理 - 软中断优化下
Linux内核实时机制7 - 实时改造机理 - 软中断优化下 https://blog.csdn.net/u010971180/article/details/145722641以下分别以Linux4.19、Linux5.4、Linux5.10、Linux5.15 展开分析,深入社区实时改造机理的软中断优化过程。https://blog.csdn.net/weixin_41028621/article/det…...
企业知识管理平台重构数字时代知识体系与智能服务网络
内容概要 现代企业知识管理平台的演进呈现出全生命周期管理与智能服务网络构建的双重特征。通过四库体系(知识采集库、加工库、应用库、评估库)的协同运作,该系统实现了从知识沉淀、结构化处理到价值释放的完整闭环。其中,知识图…...
大数据组件(四)快速入门实时数据湖存储系统Apache Paimon(3)
Paimon的下载及安装,并且了解了主键表的引擎以及changelog-producer的含义参考: 大数据组件(四)快速入门实时数据湖存储系统Apache Paimon(1) 利用Paimon表做lookup join,集成mysql cdc等参考: 大数据组件(四)快速入门实时数据…...
SVN把英文换中文
原文链接:SVN设置成中文版本 都是英文,换中文 Tortoise SVN 安装汉化教程(乌龟SVN) https://pan.quark.cn/s/cb6f2eee3f90 下载中文包...
Ubuntu 的RabbitMQ安装
目录 1.安装Erlang 查看erlang版本 退出命令 2. 安装 RabbitMQ 3.确认安装结果 4.安装RabbitMQ管理界面 5.启动服务并访问 1.启动服务 2.查看服务状态 3.通过IP:port 访问界面 4.添加管理员用户 a)添加用户名:admin,密码࿱…...
基于WebRTC与AI大模型接入EasyRTC:打造轻量级、高实时、强互动的嵌入式音视频解决方案
随着物联网和嵌入式技术的快速发展,嵌入式设备对实时音视频通信的需求日益增长。然而,传统的音视频解决方案往往存在体积庞大、实时性差、互动体验不佳等问题,难以满足嵌入式设备的资源限制和应用场景需求。 针对以上痛点,本文将介…...
QML 实现一个动态的启动界面
QML 实现一个动态的启动界面 一、效果查看二、源码分享三、所用到的资源下载 一、效果查看 二、源码分享 工程结构 main.qml import QtQuick import QtQuick.Controls import QtQuick.Dialogs import Qt.labs.platformWindow {id:windowwidth: 640height: 400visible: truetit…...
智能预警系统标准化处理流程
在当今数字化时代,IT系统的稳定运行对企业的业务连续性至关重要。为了及时发现和响应系统异常,构建智能预警系统已成为许多企业的当务之急。但仅仅拥有预警系统还不够,我们还需要一套标准化的处理流程,确保问题能够高效、有序地得到解决。 © ivwdcwso (ID: u012172506) 一…...
Unity游戏制作中的C#基础(4)数组声明和使用
一、数组的声明 在 C# 中,声明数组有多种方式,每种方式都有其适用的场景,下面为你逐一详细介绍: 1. 直接初始化声明 这种方式直观且便捷,在声明数组的同时就为其赋初值,让数组从诞生之初就拥有了具体的数据…...
tailwindcss学习03
01 入门 02 vue中接入 03 工具类优先 准备 vue.svg <svg viewBox"0 0 40 40" xmlns"http://www.w3.org/2000/svg"> <defs> <linearGradient x1"50%" y1"0%" x2"50%" y2"100%" id"a"&…...
超短脉冲激光自聚焦效应
前言与目录 强激光引起自聚焦效应机理 超短脉冲激光在脆性材料内部加工时引起的自聚焦效应,这是一种非线性光学现象,主要涉及光学克尔效应和材料的非线性光学特性。 自聚焦效应可以产生局部的强光场,对材料产生非线性响应,可能…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
visual studio 2022更改主题为深色
visual studio 2022更改主题为深色 点击visual studio 上方的 工具-> 选项 在选项窗口中,选择 环境 -> 常规 ,将其中的颜色主题改成深色 点击确定,更改完成...
高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
cf2117E
原题链接:https://codeforces.com/contest/2117/problem/E 题目背景: 给定两个数组a,b,可以执行多次以下操作:选择 i (1 < i < n - 1),并设置 或,也可以在执行上述操作前执行一次删除任意 和 。求…...
OkHttp 中实现断点续传 demo
在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...
Typeerror: cannot read properties of undefined (reading ‘XXX‘)
最近需要在离线机器上运行软件,所以得把软件用docker打包起来,大部分功能都没问题,出了一个奇怪的事情。同样的代码,在本机上用vscode可以运行起来,但是打包之后在docker里出现了问题。使用的是dialog组件,…...
Java线上CPU飙高问题排查全指南
一、引言 在Java应用的线上运行环境中,CPU飙高是一个常见且棘手的性能问题。当系统出现CPU飙高时,通常会导致应用响应缓慢,甚至服务不可用,严重影响用户体验和业务运行。因此,掌握一套科学有效的CPU飙高问题排查方法&…...
以光量子为例,详解量子获取方式
光量子技术获取量子比特可在室温下进行。该方式有望通过与名为硅光子学(silicon photonics)的光波导(optical waveguide)芯片制造技术和光纤等光通信技术相结合来实现量子计算机。量子力学中,光既是波又是粒子。光子本…...
Kafka入门-生产者
生产者 生产者发送流程: 延迟时间为0ms时,也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于:异步发送不需要等待结果,同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...
