Linux进阶篇:深入理解线程

开篇:本文解决什么问题?

  • 不清楚线程/进程区别和线程本质特性
  • 不熟悉pthread基本操作和线程同步
  • 缺乏线程池设计能力,无法解决高并发性能问题

一、线程基础概念

1. 线程的定义与特性

线程是进程内的执行单元,是操作系统调度的基本单位 ,一个进程可包含多个线程,所有线程共享 进程的地址空间(代码段、数据段、堆等),仅拥有独立的栈空间和寄存器上下文。

轻量级:线程创建与切换的开销远小于进程

资源共享:线程间可直接访问进程的全局变量、文件描述符等资源

并发执行:多个线程可在同一进程内并发执行,提高程序执行效率

2. 线程与进程的区别

|---------|-------------|--------------|
| 维度 | 进程 | 线程 |
| 资源分配 | 拥有独立的地址空间 | 共享进程的地址空间 |
| 调度单位 | 操作系统分配资源的单位 | 操作系统调度执行的单位 |
| 创建/切换开销 | 大 | 小 |
| 通信方式 | 需借助IPC机制 | 直接访问共享资源即可通信 |

二、Linux线程核心操作函数

Linux下通过POSIX线程库(pthread)实现线程操作,核心函数涵盖线程的创建、退出、等待等,需包含头文件**<pthread.h>** ,编译时需链接线程库**-lpthread**。

1. 线程创建:pthread_create()

cpp 复制代码
int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void *), void *arg);

参数:

thread :传出参数,保存创建的线程ID

attr :线程属性(如分离属性、栈大小等),设为NULL则使用默认属性

start_routine :线程执行的函数入口,函数返回值和参数均为 void* 类型

arg :传递给线程函数的参数

返回值:成功返回0,失败返回错误码。

生命周期:如果主线程先于子进程结束且未调用pthread_join,进程就有可能会在子进程完成前终止,导致子进程被强制结束

2. 线程退出:pthread_exit()

cpp 复制代码
void pthread_exit(void *retval);

用于线程主动退出, retval 为线程的返回值,可通过 pthread_join() 获取。

注意:retval必须是全局变量或者动态分配创建的内存。

3. 线程等待:pthread_join()

cpp 复制代码
int pthread_join(pthread_t thread, void **retval);

阻塞等待指定线程结束,并获取线程的返回值( retval ),适用于需要等待线程执行结果的场景。

三、线程同步与互斥机制

线程共享进程资源,多线程并发访问临界资源(如全局变量)时,会出现数据竞争问题,需通过同步互斥机制保证数据一致性。

1. 互斥锁(Mutex)

互斥锁是最常用的同步机制,通过加锁-解锁保证同一时刻只有一个线程 访问临界资源。

核心函数:

cpp 复制代码
// 创建互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
// 加锁
int pthread_mutex_lock(pthread_mutex_t *mutex);
// 解锁
int pthread_mutex_unlock(pthread_mutex_t *mutex);
// 销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);

参数:

mutex:指向pthread_mutex_t的指针(初始化该地址的变量,对创建的互斥锁上锁,用于对创建的互斥锁解锁,创建的互斥锁销毁)

attr:执行pthread_mutexattr_t 的指针,用于指定互斥锁的属性,通常为NULL

使用原则:临界区代码必须被互斥锁包裹,加锁后需及时解锁,避免死锁。

2. 条件变量(Condition Variable)

条件变量用于线程间的条件等待,配合互斥锁使用,实现线程的同步执行(如生产者-消费者模型)。

核心函数:

cpp 复制代码
// 创建条件变量
int pthread_cond_init(pthread_cond_t *cond, const pthread_condattr_t *attr);
// 等待条件满足(会释放互斥锁,被唤醒后重新获取)
int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
// 唤醒一个等待的线程
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒所有等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
// 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);

参数:

cond:指向pthread_cond_t的指针,指向的条件变量

attr:条件变量属性,一般设为NULL

mutex:指向互斥锁

两个或多个线程共享同一缓冲区,其中一个线程作为"生产者"的角色,不断为缓冲区添加资源,而"消费者"取走数据

  • 生产者和消费者必须互斥使用冲区
  • 缓冲区满时,生产者不能生产资源
  • 缓冲区空时,消费者不能获取资源

读写锁

读锁:共享锁,可以被多个线程持有,只要没有线程持有写锁

写锁:独占锁,只能被一个线程持有,且不允许任何线程持有读锁

**not_full**条件变量:让缓冲区满时生产者阻塞,消费者消费后唤醒

**not_empty**条件变量:让缓冲区空时消费者阻塞,生产者生产后唤醒

|-----|--------------------|--------------------------------|
| | 互斥锁 | 读写锁 |
| 定义 | ​任意时刻,只有一个线程运行 | 允许多个读者 同时运行,但写着必须独占资源​ |
| 规则 | 只要有线程持有锁,其他线程必须等待 | 持有读锁时,可继续加读锁;持有写锁时,​独占资源 |
| ​场景 | 读写较为频繁,读和写的频率一致 | 读多写少的情况​ |

3. 信号量(Semaphore)

信号量是更通用的同步机制,分为二值信号量 (实现互斥)和计数信号量 (控制资源并发访问数),需包含头文件**<semaphore.h>**。

核心函数:

cpp 复制代码
// 初始化信号量
int sem_init(sem_t *sem, int pshared, unsigned int value);
// P操作(信号量减1,无资源则阻塞)
int sem_wait(sem_t *sem);
// V操作(信号量加1,唤醒阻塞线程)
int sem_post(sem_t *sem);
// 销毁信号量
int sem_destroy(sem_t *sem);
同步方法 互斥锁 读写锁 条件变量 信号量
用途 线程独享资源 优化同类不影响其他线程的调度 协调线程的执行性顺序 控制线程并发的数量
场景 线程需要单独运行 读多写少,同类不影响其他线程的线程同时运行 需要等待特殊条件 限制连接数量,控制线程池的大小
​并发效率 低​ 中​ 中​ 中​

四、线程池设计与实现

线程池是一种复用线程的技术,预先创建一定数量的线程,处理大量短期任务,避免频繁创建/销毁线程的开销。

1. 线程池的核心组成

任务队列:存储待执行的任务,通常用链表或队列实现

工作线程:预先创建的线程,循环从任务队列中获取任务并执行

管理线程(可选):负责监控线程池状态,动态调整工作线程数量

同步机制:通过互斥锁+条件变量实现任务队列的线程安全访问

2. 线程池的工作流程

1.初始化线程池,创建指定数量的工作线程

  1. 向线程池添加任务,将任务放入任务队列

  2. 工作线程通过条件变量等待任务,获取任务后执行

  3. 任务执行完毕,工作线程继续等待新任务

  4. 销毁线程池时,停止接收新任务,等待所有工作线程执行完毕后释放资源

3. 线程池关键设计点

任务队列的线程安全:通过互斥锁保证任务的添加和获取操作原子性

线程池的动态扩容:根据任务队列的长度,动态增加/减少工作线程数量

任务的优先级:设计优先级队列,让高优先级任务先执行

五、线程安全与死锁

1. 线程安全

线程安全是指多线程并发访问时,程序的执行结果与单线程执行结果一致。实现线程安全的手段包括:

  • 使用互斥锁、条件变量等同步机制
  • 避免使用全局变量,或对全局变量进行加锁保护
  • 使用线程局部存储(TLS),让每个线程拥有独立的变量副本

可重入函数 ( _r ):指一个函数可以被安全的中断,且在前一次调用尚未完成时可以被再次调用,能够正确执行,不会导致数据损失或产生不确定结果。

strtok 是一个不可重入函数,不能在多个线程中使用

strtok_r 可重入函数

2. 死锁及解决方法

死锁是指多个线程互相持有对方需要的资源,导致所有线程都无法继续执行的状态。死锁的产生需满足四个必要条件:互斥、请求与保持、不剥夺、循环等待

解决方法:

  • 按顺序申请资源,打破循环等待条件
  • 限时申请资源,获取不到则释放已持有的资源
  • 使用死锁检测算法,发现死锁后主动解除
相关推荐
2301_797312262 小时前
学习Java32天
java·开发语言
Mr.朱鹏2 小时前
分布式接口幂等性实战指南【完整版】
java·spring boot·分布式·sql·spring·云原生·幂等
TAEHENGV2 小时前
提醒列表模块 Cordova 与 OpenHarmony 混合开发实战
android·java·harmonyos
yong99902 小时前
基于压缩感知与后向投影算法的合成孔径雷达成像实现
算法
蒙奇D索大2 小时前
【数据结构】排序算法精讲 | 插入排序全解:稳定性、复杂度与实战代码剖析
数据结构·算法·排序算法
源码获取_wx:Fegn08952 小时前
基于springboot + vue宠物寄养系统
java·vue.js·spring boot·后端·spring·宠物
刘永鑫Adam2 小时前
Nature Methods | 诸奇赟组-Scikit-bio:用于生物组学数据分析的基础Python库
人工智能·python·算法·机器学习·数据分析
hweiyu002 小时前
查找算法:分类及特点
算法·分类
youngee112 小时前
hot100-51搜索二维矩阵
数据结构·算法·矩阵