Linux----线程

news/2025/2/23 5:41:25

一、基础概念对比

特性进程 (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. 关键特征

  • 线程IDpthread_t 类型,进程内唯一
  • 独立栈空间:每个线程拥有独立调用栈
  • 共享资源:全局变量、堆内存、文件描述符等

三、线程创建与管理

1. 创建函数原型

#include <pthread.h>
int pthread_create(pthread_t *thread, 
                   const pthread_attr_t *attr,
                   void *(*start_routine)(void *), 
                   void *arg);
参数详解表
参数类型作用说明
threadpthread_t*输出参数,存储新线程ID
attrpthread_attr_t*线程属性(NULL使用默认属性):<br>▪ 栈大小<br>▪ 调度策略<br>▪ 分离状态
start_routinevoid* (*)(void*)线程入口函数(返回值为线程退出状态)
argvoid*传递给入口函数的参数
返回值
  • 成功返回0
  • 失败返回错误码(非errno值,需用strerror转换)

2. 编译指令

gcc program.c -lpthread -o program  # 必须链接pthread库

四、线程生命周期管理

1. 线程终止方式

(1) 显式调用退出函数
void* worker(void* arg) {
    int* heap_result = malloc(sizeof(int));
    *heap_result = 100;
    pthread_exit((void*)heap_result);  // 正确方式:堆内存传递

    // static int static_result = 200;  // 替代方案:静态变量
    // pthread_exit((void*)&static_result);
}

关键特性

  • 状态值通过pthread_join()获取
  • 返回值必须使用堆内存或全局/静态变量
  • 主线程调用时仅结束自身执行流
(2) 入口函数返回
void* worker(void* arg) {
    static int result = 200;  // 必须使用静态存储期变量
    return (void*)&result;     // 等效于pthread_exit()
}

禁止行为

int local_var = 300;
return (void*)&local_var;  // 错误!栈空间失效
(3) 被其他线程取消
// 取消请求端
pthread_cancel(target_tid);

// 被取消线程端
void* worker(void* arg) {
    pthread_setcanceltype(PTHREAD_CANCEL_DEFERRED, NULL);
    while(1) {
        pthread_testcancel();  // 设置取消点
        /* 长时间操作 */
    }
}

取消类型对比

类型行为特征设置函数
PTHREAD_CANCEL_DEFERRED延迟到下一个取消点pthread_setcanceltype()
PTHREAD_CANCEL_ASYNCHRONOUS立即终止(可能破坏数据)pthread_setcanceltype()
(4) 进程级终止
int main() {
    pthread_t tid;
    pthread_create(&tid, NULL, worker, NULL);
    
    // return 0;        // 错误!触发exit()终止所有线程
    pthread_exit(NULL); // 正确:仅结束主线程
}

进程终止规则

  • exit()立即终止整个进程
  • 主线程return会隐式调用exit()
  • 建议主线程始终使用pthread_exit()

2. 状态回收机制

(1) 阻塞回收(Joinable模式)
void* status;
int ret = pthread_join(tid, &status);
if(ret == 0) {
    printf("退出码: %d\n", *(int*)status);
    free(status);  // 必须释放堆内存
}

限制条件

  • 每个线程只能被join一次
  • 已分离线程无法join
(2) 自动回收(Detached模式)
// 创建时设置分离属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, worker, NULL);

// 运行时分离
pthread_detach(existing_tid);

特性

  • 线程终止后自动回收资源
  • 无法获取返回值
  • 适用于后台任务线程

五、线程资源管理

1. 清理函数机制

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;
}

触发条件

  1. pthread_cleanup_pop(非零值)
  2. 线程通过pthread_exit()退出
  3. 被其他线程取消

编码规范

  • push/pop必须成对出现
  • 建议在可能被取消的代码段前注册
  • 栈式管理(后进先出)

2. 属性管理

(1) 完整属性设置流程
pthread_attr_t attr;
pthread_attr_init(&attr);  // 初始化

// 设置分离状态
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);

// 设置栈大小(2MB示例)
size_t stack_size = 2 * 1024 * 1024;
void* stack_addr = malloc(stack_size);
pthread_attr_setstack(&attr, stack_addr, stack_size);

pthread_create(&tid, &attr, worker, NULL);

pthread_attr_destroy(&attr);  // 销毁属性
(2) 常用属性API
函数功能描述
pthread_attr_setdetachstate设置分离/结合状态
pthread_attr_setstacksize设置线程栈大小
pthread_attr_setguardsize设置栈溢出保护区大小
pthread_attr_setschedpolicy设置调度策略(FIFO/RR等)

六、线程同步机制

1. 互斥锁完整实现

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void* thread_func(void* arg) {
    pthread_mutex_lock(&mutex);
    /* 临界区操作 */
    pthread_mutex_unlock(&mutex);
    return NULL;
}

// 动态初始化方式
pthread_mutex_init(&mutex, NULL);
/* ... */
pthread_mutex_destroy(&mutex);

2. 同步机制对比

机制适用场景优点缺点
互斥锁共享资源访问控制简单高效可能产生死锁
读写锁读多写少场景提高读并发性能写线程可能饿死
条件变量线程间状态通知精确唤醒机制需配合互斥锁使用
信号量资源数量控制跨进程可用功能相对基础

3. 全局变量竞争解决方案

pthread_mutex_t counter_mutex = PTHREAD_MUTEX_INITIALIZER;
int global_counter = 0;

void* counter_thread(void* arg) {
    for(int i=0; i<100000; ++i) {
        pthread_mutex_lock(&mutex);
        global_counter++;  // 原子操作
        pthread_mutex_unlock(&mutex);
    }
    return NULL;
}

优化建议

  • 尽量减小临界区范围
  • 避免嵌套锁
  • 使用trylock避免死锁

七、高级主题与最佳实践

1. 返回值处理规范

(1) 简单状态码
// 传递整型值
pthread_exit((void*)(intptr_t)error_code);

// 接收端
int code = (int)(intptr_t)status;
(2) 复杂数据结构
struct Result {
    int code;
    char message[256];
};

void* worker(void* arg) {
    struct Result* res = malloc(sizeof(struct Result));
    /* 填充数据 */
    pthread_exit(res);
}

// 接收端
struct Result* res = (struct Result*)status;
free(res);

2. 线程设计准则

  1. 资源管理三原则

    • 谁分配谁释放
    • 退出前释放非共享资源
    • 使用RAII模式管理资源
  2. 锁使用规范

    // 推荐加锁方式
    pthread_mutex_lock(&mutex);
    do {
        /* 临界区操作 */
    } while(0);
    pthread_mutex_unlock(&mutex);
    
    // 避免的写法
    if(condition) pthread_mutex_unlock(&mutex);  // 易漏解锁
    
  3. 错误处理模板

    int ret = pthread_create(&tid, NULL, worker, NULL);
    if(ret != 0) {
        fprintf(stderr, "线程创建失败: %s\n", strerror(ret));
        exit(EXIT_FAILURE);
    }
    

3. 调试技巧

  1. 死锁检测

    • 使用pthread_mutex_trylock()测试锁状态
    • 记录加锁顺序
    • 使用Valgrind的Helgrind工具
  2. 性能分析

    # 使用perf分析锁竞争
    perf record -g -- ./program
    perf report
    

     八、完整生命周期图示

graph TD
    A[线程创建] --> B{执行阶段}
    B -->|正常完成| C[资源回收]
    B -->|被取消| D[清理处理]
    C --> E[线程终止]
    D --> E
    E --> F[系统回收TID]
    
    style B fill:#f9f,stroke:#333,stroke-width:2px
    style C fill:#9f9,stroke:#333
    style D fill:#f99,stroke:#333
    
    subgraph 关键状态
    B
    C
    D
    end

九、常见问题解决方案

1. 僵尸线程问题

现象:线程终止但未回收,占用系统资源
解决方案

// 方案1:及时join
void* retval;
pthread_join(tid, &retval);
free(retval);

// 方案2:设置分离属性
pthread_attr_t attr;
pthread_attr_init(&attr);
pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED);
pthread_create(&tid, &attr, worker, NULL);

2. 返回值内存泄漏

错误示例

void* worker() {
    int result = 42;
    pthread_exit(&result);  // 栈内存泄露!
}

正确实践

void* worker() {
    int* result = malloc(sizeof(int));  // 堆内存
    *result = 42;
    pthread_exit(result);
}

3. 取消点设置不足

问题表现:取消请求长期不响应
优化方案

void* worker(void* arg) {
    pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);
    while(1) {
        pthread_testcancel();  // 每循环添加取消点
        /* 长时间操作 */
    }
    return NULL;
}

十、扩展知识

1. 线程与进程对比

特性进程线程
资源开销高(独立地址空间)低(共享地址空间)
通信方式管道、共享内存、信号等全局变量、互斥锁、条件变量
容错性一个进程崩溃不影响其他线程崩溃导致整个进程终止
上下文切换成本

2. 可重入函数设计

安全函数特征

  • 不使用静态变量
  • 不调用非可重入函数
  • 所有数据通过参数传递

示例对比

// 不安全版本
char* strtok(char* str, const char* delim) {
    static char* buffer;  // 静态变量
    /* ... */
}

// 可重入版本
char* strtok_r(char* str, const char* delim, char** saveptr) {
    /* 使用传入的saveptr保存状态 */
}

十一、练习

    练习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 1
		if (i == n-1)
		{ 
			msg[i].len  = f_len - (f_len/n)*(n-1);
		}else 
		{
			msg[i].len  = f_len/n;
		}
#endif
		ret = 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;
}
练习5、创建两个线程,线程1 打印 hello,线程2 打印 world,预期效果在屏幕上严格打印hello world
#include<stdio.h>
#include<pthread.h>
#include<errno.h>
#include<unistd.h>

pthread_mutex_t mutex;
int flag = 1;//为1时线程1执行,为2时线程2执行

void *do_something(void *arg)
{
    while(1)
    {
        pthread_mutex_lock(&mutex);

		if(flag ==1)
		{
			printf("Hello ");
			flag = 2;//交换执行权限
		}

		pthread_mutex_unlock(&mutex);
        usleep(10000);
    }
    return NULL;
}

void *do_something1(void *arg)
{
    while(1)
    {
		pthread_mutex_lock(&mutex);
		
		if(flag == 2)
		{
			printf("World!\n");
			flag = 1;//交换执行权限
		}
		
		pthread_mutex_unlock(&mutex);
		usleep(10000);
	}
    return NULL;
}

int main(int argc, const char *argv[])
{
    pthread_t tid1, tid2;
    int ret, ret1;

    pthread_mutex_init(&mutex, NULL); // 初始化一把锁

    if ((ret = pthread_create(&tid1, NULL, do_something, NULL)) != 0)
    {
        errno = ret;
        perror("pthread_create for do_something fail");
        return -1;
    }

    if ((ret1 = pthread_create(&tid2, NULL, do_something1, NULL)) != 0)
    {
        errno = ret1;
        perror("pthread_create for do_something1 fail");
        return -1;
    }

    pthread_join(tid1, NULL);
    pthread_join(tid2, NULL);

    pthread_mutex_destroy(&mutex);

    return 0;
}

 



http://www.niftyadmin.cn/n/5863019.html

相关文章

DeepSeek掘金——DeepSeek-R1+ML混合欺诈检测

DeepSeek-R1+ML混合欺诈检测 在数字交易占主导地位的时代,欺诈已成为一种复杂且无处不在的威胁。到2026年,全球欺诈损失预计将达到430亿美元,仅在美国,每14秒就发生一起身份盗窃案。随着犯罪分子利用人工智能(AI)发起前所未有的攻击,企业面临一个紧迫的问题:他们如何才…

Camera ISP Pipeline(相机图像信号处理管线)

文章目录 Camera ISP Pipeline&#xff08;相机图像信号处理管线&#xff09;1. 基础信号处理2. 色彩处理3. 图像增强4. 后期处理重要性应用场景 Camera ISP Pipeline&#xff08;相机图像信号处理管线&#xff09; Camera ISP Pipeline&#xff08;相机图像信号处理管线&…

基于SpringBoot的“宠物救助及领养平台”的设计与实现(源码+数据库+文档+PPT)

基于SpringBoot的“宠物救助及领养平台”的设计与实现&#xff08;源码数据库文档PPT) 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SpringBoot 工具&#xff1a;IDEA/Ecilpse、Navicat、Maven 系统展示 系统整体功能图 系统首页界面 系统注册页面…

CellChat前沿:spaCI:通过自适应图模型破译空间蜂窝通信

1.论文原名&#xff1a;spaCI: deciphering spatial cellular communications through adaptive graph model 2.发表日期&#xff1a;2023 摘要&#xff1a; 细胞间通讯对于生物信号传导至关重要&#xff0c;在复杂疾病中发挥重要作用。空间细胞转录组学&#xff08;SCST&…

k2路由器登录校园网

教程1刷入Breed&#xff0c;并手动刷入Padavan固件&#xff1a;斐讯K1、K2、K2P 刷机、刷入Breed 辅助工具 | tb (tbvv.net) Padavan下载网址&#xff1a; 我用的是&#xff1a; Padavan 登录的网址是 192.168.123.1 Padavan配置教程&#xff1a; 先用网线连上校园网&#…

Eclipse导入forge-1.21.x

forge中文文档 forge英文文档 下载forge mdk forge mdk下载地址 生成项目 将下载的mdk解压&#xff0c;放到全英文路径的目录下&#xff0c; 打开命令行&#xff0c;执行gradlew genEclipseRuns&#xff0c;出现 使用eclipse则执行gradlew eclipse 使用ides直接导入bui…

Vue 不同大版本与 Node.js 版本匹配的详细参数

Vue 2.x 与 Node.js 版本匹配: Vue 2.x 细分版本建议 Node.js 版本理由支持状态2.0 - 2.610.x - 14.x这些 Node.js 版本在 Vue 2.x 早期开发和维护阶段广泛使用&#xff0c;能提供稳定运行环境&#xff0c;适配当时常用构建工具和依赖包Node.js 10.x 维护期已结束&#xff1b;…

深入解析 Linux 文件系统:EXT4、NTFS、NFS、CIFS 等的特点与应用(中英双语)

深入解析 Linux 文件系统&#xff1a;EXT4、NTFS、NFS、CIFS 等的特点与应用 在 Linux 和其他操作系统中&#xff0c;文件系统&#xff08;File System&#xff09; 是管理存储设备数据的重要组成部分。不同的文件系统具有不同的结构、特点和适用场景。例如&#xff0c;EXT4 是…