基础概念
同步与互斥核心定义
| 类型 |
核心含义 |
关键特征 |
| 同步 |
两个或多个任务并发时,必须按照特定先后顺序运行 |
有序性、协作性 |
| 互斥 |
两个或多个任务并发时,不能同时进行某一特定操作 |
排他性、独占性 |
补充基础概念
临界资源(Critical Resources)
- 定义:多个进程或线程有可能同时访问的资源(如变量、链表、文件等)
- 特点:需通过同步/互斥机制保护,避免并发访问冲突
临界区(Critical Zone)
- 定义:访问临界资源的代码段
- 要求:进入临界区前必须申请资源(P操作),离开后必须释放资源(V操作)
P操作与V操作(信号量基础操作)
| 操作 |
作用 |
类比场景 |
资源数变化 |
| P操作 |
申请资源(进入临界区前执行) |
进停车场前申请停车卡 |
申请成功:资源数-1;失败:阻塞等待或放弃 |
| V操作 |
释放资源(离开临界区后执行) |
出停车场后归还停车卡 |
释放成功:资源数+1 |
互斥锁(Mutex)
基本逻辑
- 本质:二值信号量(仅两种状态:锁定/未锁定)
- 核心作用:确保多线程对临界资源的互斥访问
- 工作原理:
- 线程需访问临界区时,必须先获取互斥锁
- 若锁已被占用,其他线程阻塞/非阻塞等待
- 持有锁的线程离开临界区后,必须释放锁,其他线程方可竞争
操作流程
- 定义锁资源(需被多个线程访问,通常为全局变量或参数传递)
- 初始化锁资源(静态/动态初始化)
- 上锁(进入临界区前,提供阻塞属性)
- 解锁(离开临界区后)
- 不再使用时销毁锁
核心操作接口
初始化与销毁
// 动态初始化
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);
// 销毁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
// 静态初始化(无需销毁)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
- 参数说明:
mutex:需初始化/销毁的互斥锁变量地址
attr:互斥锁属性(默认设为NULL)
上锁操作
int pthread_mutex_lock(pthread_mutex_t *mutex); // 阻塞等待(推荐优先使用)
int pthread_mutex_trylock(pthread_mutex_t *mutex); // 非阻塞(失败立即返回)
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime); // 超时阻塞
解锁操作
int pthread_mutex_unlock(pthread_mutex_t *mutex); // 释放锁(仅持有锁的线程可调用)
示例代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
// 临界资源(全局变量)
int CriticalResource = 0;
void *TaskFunc(void *arg) {
pthread_mutex_t *mutex = (pthread_mutex_t *)arg;
while (1) {
// 上锁
printf("线程A在等待上锁..\n");
pthread_mutex_lock(mutex);
printf("线程A在成功上锁..\n");
////////////////以下为临界区///////////////
if (CriticalResource % 2) {
printf("[%d]是一个奇数\n", CriticalResource);
} else {
printf("[%d]是一个偶数\n", CriticalResource);
}
////////////////以上为临界区///////////////
// 解锁
pthread_mutex_unlock(mutex);
printf("线程A在释放锁资源..\n");
sleep(1);
}
}
int main(int argc, char const *argv[]) {
// 1. 定义锁资源
pthread_mutex_t mutex;
// 2. 初始化锁
pthread_mutex_init(&mutex, NULL);
pthread_t tid;
pthread_create(&tid, NULL, TaskFunc, (void *)&mutex);
while (1) {
// 上锁
printf("线程B在等待上锁..\n");
pthread_mutex_lock(&mutex);
printf("线程B在成功上锁..\n");
////////////////以下为临界区///////////////
CriticalResource = rand() % 1000;
////////////////以上为临界区///////////////
// 解锁
pthread_mutex_unlock(&mutex);
printf("线程B在释放锁资源..\n");
sleep(1);
}
// 3. 销毁锁(实际因无限循环无法执行,仅作演示)
pthread_mutex_destroy(&mutex);
return 0;
}
读写锁(RWLock)
基本逻辑
- 设计初衷:解决互斥锁“读写一律互斥”的效率问题
- 核心机制:
- 读操作:共享访问(多个线程可同时持有读锁)
- 写操作:排他访问(仅一个线程可持有写锁,与读锁互斥)
- 适用场景:读操作远多于写操作的场景(如缓存、配置文件访问)
操作流程
- 定义读写锁资源(变量)
- 初始化读写锁(静态/动态)
- 上锁(读锁/写锁,根据操作类型选择)
- 读锁 (共享的,只要没有其他的写锁存在可以允许读锁并发)
- 写锁 (互斥的,只要有任何的读锁或写锁存在都无法上锁)
- 解锁(操作完成后释放)
- 不再使用时销毁
核心操作接口
初始化与销毁
// 动态初始化
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);
// 销毁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);
// 静态初始化(无需销毁)
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;
上锁操作
| 操作类型 |
阻塞版本 |
非阻塞版本 |
超时版本 |
| 读锁 |
pthread_rwlock_rdlock(rwlock) |
pthread_rwlock_tryrdlock(rwlock) |
pthread_rwlock_timedrdlock(rwlock, abstime) |
| 写锁 |
pthread_rwlock_wrlock(rwlock) |
pthread_rwlock_trywrlock(rwlock) |
pthread_rwlock_timedwrlock(rwlock, abstime) |
解锁操作
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock); // 读锁/写锁通用解锁接口
访问规则
| 当前锁状态 |
申请读锁 |
申请写锁 |
| 无锁 |
成功(共享) |
成功(排他) |
| 已有1个/多个读锁 |
成功(共享) |
阻塞/失败 |
| 已有1个写锁 |
阻塞/失败 |
阻塞/失败 |
写饥饿问题与解决方案
写饥饿现象
- 原因:默认读优先(
PTHREAD_RWLOCK_PREFER_READER_NP),读锁持续占用时,写锁永远无法获取
- 后果:写操作长期阻塞,数据更新延迟
解决方案:设置写优先属性
通过pthread_rwlockattr_setkind_np设置属性,确保写锁优先:
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);
示例代码
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <time.h>
// 全局读写锁资源
pthread_rwlock_t rwlock;
// 临界资源
int Num = 0;
void *TaskFunc(void *arg) {
while (1) {
printf("[%#lx]正在上读锁...\n", pthread_self());
pthread_rwlock_rdlock(&rwlock);
printf("[%#lx]成功上读锁...\n", pthread_self());
// 只读访问临界资源
printf("Num:%d\n", Num);
pthread_rwlock_unlock(&rwlock);
printf("[%#lx]释放读锁...\n", pthread_self());
}
}
int main(int argc, char const *argv[]) {
// 初始化读写锁属性
pthread_rwlockattr_t attr;
pthread_rwlockattr_init(&attr);
// 设置写优先属性
pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP);
// 初始化读写锁(关联属性)
pthread_rwlock_init(&rwlock, &attr);
// 创建20个读线程
pthread_t tid;
for (int i = 0; i < 20; i++) {
pthread_create(&tid, NULL, TaskFunc, (void *)&rwlock);
printf("创建读线程:%#lx\n", tid);
pthread_detach(tid); // 分离线程,自动释放资源
}
// 随机种子初始化
srand(time(NULL));
// 主函数作为写线程
while (1) {
printf("【主函数】正在上写锁...\n");
pthread_rwlock_wrlock(&rwlock);
printf("【主函数】成功上写锁...\n");
// 修改临界资源
Num = rand() % 1000;
sleep(3); // 模拟写操作耗时
pthread_rwlock_unlock(&rwlock);
printf("【主函数】释放写锁...\n");
}
return 0;
}
拓展知识点
死锁(Deadlock)
死锁产生的4个必要条件
- 互斥条件:资源只能被一个线程独占
- 请求与保持条件:线程持有部分资源,同时请求其他资源
- 不可剥夺条件:资源只能由持有线程主动释放,无法强制剥夺
- 循环等待条件:多个线程形成资源请求循环(如A等B的资源,B等A的资源)
死锁的解决策略
| 策略 |
核心思路 |
实现方式 |
| 预防 |
破坏4个必要条件之一 |
1. 允许资源剥夺;2. 按顺序申请资源;3. 一次性申请所有资源 |
| 避免 |
动态检查资源分配,避免进入不安全状态 |
银行家算法(计算资源分配后的安全性) |
| 检测与恢复 |
允许死锁发生,定期检测并恢复 |
1. 终止死锁线程;2. 剥夺死锁线程的资源;3. 回滚线程执行 |
编程中的避免技巧
- 按固定顺序申请资源(如锁A→锁B,所有线程统一顺序)
- 避免长时间持有锁(临界区代码尽量简洁)
- 使用带超时的锁(如
pthread_mutex_timedlock),超时后释放已持有资源
线程取消与解锁
线程取消相关概念
- 取消状态:
PTHREAD_CANCEL_ENABLE(允许取消,默认)/ PTHREAD_CANCEL_DISABLE(禁止取消)
- 取消类型:
PTHREAD_CANCEL_DEFERRED(延迟取消,默认,仅在取消点响应)/ PTHREAD_CANCEL_ASYNCHRONOUS(异步取消,立即响应)
- 取消点:线程可能响应取消的函数(如
sleep、pthread_mutex_lock等系统调用)
取消处理函数(确保解锁)
当线程被取消时,需通过取消处理函数释放已持有的锁,避免死锁:
#include <pthread.h>
// 取消处理函数(释放锁)
void cleanup_handler(void *arg) {
pthread_mutex_t *mutex = (pthread_mutex_t *)arg;
pthread_mutex_unlock(mutex); // 确保锁被释放
printf("线程被取消,已释放锁\n");
}
void *task(void *arg) {
pthread_mutex_t *mutex = (pthread_mutex_t *)arg;
// 注册取消处理函数(栈结构,先注册后执行)
pthread_cleanup_push(cleanup_handler, mutex);
pthread_mutex_lock(mutex);
printf("线程持有锁,开始执行任务\n");
// 模拟任务执行(取消点)
sleep(10);
pthread_mutex_unlock(mutex);
// 取消处理函数的弹出(参数0表示不执行处理函数)
pthread_cleanup_pop(0);
return NULL;
}
- 关键:
pthread_cleanup_push与pthread_cleanup_pop必须成对出现,确保处理函数被调用
POSIX信号量(Semaphore)
与互斥锁的区别
| 特性 |
互斥锁 |
POSIX信号量 |
| 本质 |
二值信号量(0/1) |
计数信号量(0~N) |
| 适用场景 |
线程间互斥访问临界资源 |
线程/进程间同步、限流(如控制并发数) |
| 所有权 |
持有锁的线程必须解锁 |
无所有权,任意线程可释放 |
核心接口
#include <semaphore.h>
// 初始化信号量(pshared=0:线程间共享;pshared!=0:进程间共享)
int sem_init(sem_t *sem, int pshared, unsigned int value);
// P操作(申请资源,value--,value=0时阻塞)
int sem_wait(sem_t *sem); // 阻塞
int sem_trywait(sem_t *sem); // 非阻塞
int sem_timedwait(sem_t *sem, const struct timespec *abs_timeout); // 超时
// V操作(释放资源,value++)
int sem_post(sem_t *sem);
// 销毁信号量
int sem_destroy(sem_t *sem);
应用示例:限流(最多3个线程并发执行)
#include <stdio.h>
#include <pthread.h>
#include <semaphore.h>
#include <unistd.h>
sem_t sem; // 信号量(计数=3)
void *task(void *arg) {
sem_wait(&sem); // P操作(限流)
printf("线程[%#lx]开始执行\n", pthread_self());
sleep(2); // 模拟任务耗时
printf("线程[%#lx]执行完成\n", pthread_self());
sem_post(&sem); // V操作(释放名额)
return NULL;
}
int main() {
sem_init(&sem, 0, 3); // 初始化信号量,计数=3
pthread_t tid[5];
for (int i = 0; i < 5; i++) {
pthread_create(&tid[i], NULL, task, NULL);
}
for (int i = 0; i < 5; i++) {
pthread_join(tid[i], NULL);
}
sem_destroy(&sem);
return 0;
}
条件变量(Condition Variable)
核心作用
- 实现线程间的协作同步(如“生产者-消费者”模型)
- 与互斥锁配合使用,允许线程在特定条件满足前阻塞,避免忙等
核心接口
#include <pthread.h>
// 初始化条件变量
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);
// 销毁条件变量
int pthread_cond_destroy(pthread_cond_t *cond);
// 阻塞等待条件满足(需先持有互斥锁,会释放锁并阻塞)
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);
// 超时等待条件满足
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);
// 唤醒一个等待的线程
int pthread_cond_signal(pthread_cond_t *cond);
// 唤醒所有等待的线程
int pthread_cond_broadcast(pthread_cond_t *cond);
应用示例:生产者-消费者(队列)
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#define QUEUE_SIZE 5
// 队列结构(临界资源)
int queue[QUEUE_SIZE];
int front = 0, rear = 0;
// 互斥锁与条件变量
pthread_mutex_t mutex;
pthread_cond_t not_full; // 队列非满(生产者条件)
pthread_cond_t not_empty; // 队列非空(消费者条件)
// 生产者线程:向队列添加数据
void *producer(void *arg) {
int data = 0;
while (1) {
pthread_mutex_lock(&mutex);
// 队列满时,等待"非满"条件
while ((rear + 1) % QUEUE_SIZE == front) {
printf("队列满,生产者等待...\n");
pthread_cond_wait(¬_full, &mutex);
}
// 生产数据
queue[rear] = data++;
rear = (rear + 1) % QUEUE_SIZE;
printf("生产者生产:%d\n", queue[(rear - 1) % QUEUE_SIZE]);
pthread_mutex_unlock(&mutex);
pthread_cond_signal(¬_empty); // 唤醒消费者(队列非空)
sleep(1);
}
}
// 消费者线程:从队列提取数据
void *consumer(void *arg) {
while (1) {
pthread_mutex_lock(&mutex);
// 队列为空时,等待"非空"条件
while (front == rear) {
printf("队列为空,消费者等待...\n");
pthread_cond_wait(¬_empty, &mutex);
}
// 消费数据
int data = queue[front];
front = (front + 1) % QUEUE_SIZE;
printf("消费者消费:%d\n", data);
pthread_mutex_unlock(&mutex);
pthread_cond_signal(¬_full); // 唤醒生产者(队列非满)
sleep(2);
}
}
int main() {
// 初始化
pthread_mutex_init(&mutex, NULL);
pthread_cond_init(¬_full, NULL);
pthread_cond_init(¬_empty, NULL);
// 创建线程
pthread_t prod_tid, cons_tid;
pthread_create(&prod_tid, NULL, producer, NULL);
pthread_create(&cons_tid, NULL, consumer, NULL);
// 等待线程
pthread_join(prod_tid, NULL);
pthread_join(cons_tid, NULL);
// 销毁资源
pthread_mutex_destroy(&mutex);
pthread_cond_destroy(¬_full);
pthread_cond_destroy(¬_empty);
return 0;
}