开篇:本文解决什么问题?
- 不清楚线程/进程区别和线程本质特性
- 不熟悉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.初始化线程池,创建指定数量的工作线程
-
向线程池添加任务,将任务放入任务队列
-
工作线程通过条件变量等待任务,获取任务后执行
-
任务执行完毕,工作线程继续等待新任务
-
销毁线程池时,停止接收新任务,等待所有工作线程执行完毕后释放资源
3. 线程池关键设计点
任务队列的线程安全:通过互斥锁保证任务的添加和获取操作原子性
线程池的动态扩容:根据任务队列的长度,动态增加/减少工作线程数量
任务的优先级:设计优先级队列,让高优先级任务先执行
五、线程安全与死锁
1. 线程安全
线程安全是指多线程并发访问时,程序的执行结果与单线程执行结果一致。实现线程安全的手段包括:
- 使用互斥锁、条件变量等同步机制
- 避免使用全局变量,或对全局变量进行加锁保护
- 使用线程局部存储(TLS),让每个线程拥有独立的变量副本
可重入函数 ( _r ):指一个函数可以被安全的中断,且在前一次调用尚未完成时可以被再次调用,能够正确执行,不会导致数据损失或产生不确定结果。
strtok 是一个不可重入函数,不能在多个线程中使用
strtok_r 可重入函数
2. 死锁及解决方法
死锁是指多个线程互相持有对方需要的资源,导致所有线程都无法继续执行的状态。死锁的产生需满足四个必要条件:互斥、请求与保持、不剥夺、循环等待。

解决方法:
- 按顺序申请资源,打破循环等待条件
- 限时申请资源,获取不到则释放已持有的资源
- 使用死锁检测算法,发现死锁后主动解除