同步与互斥

基础概念

同步与互斥核心定义

类型 核心含义 关键特征
同步 两个或多个任务并发时,必须按照特定先后顺序运行 有序性、协作性
互斥 两个或多个任务并发时,不能同时进行某一特定操作 排他性、独占性
对比

补充基础概念

临界资源(Critical Resources)

  • 定义:多个进程或线程有可能同时访问的资源(如变量、链表、文件等)
  • 特点:需通过同步/互斥机制保护,避免并发访问冲突

临界区(Critical Zone)

  • 定义:访问临界资源的代码段
  • 要求:进入临界区前必须申请资源(P操作),离开后必须释放资源(V操作)

P操作与V操作(信号量基础操作)

操作 作用 类比场景 资源数变化
P操作 申请资源(进入临界区前执行) 进停车场前申请停车卡 申请成功:资源数-1;失败:阻塞等待或放弃
V操作 释放资源(离开临界区后执行) 出停车场后归还停车卡 释放成功:资源数+1

互斥锁(Mutex)

基本逻辑

  • 本质:二值信号量(仅两种状态:锁定/未锁定)
  • 核心作用:确保多线程对临界资源的互斥访问
  • 工作原理:
    1. 线程需访问临界区时,必须先获取互斥锁
    2. 若锁已被占用,其他线程阻塞/非阻塞等待
    3. 持有锁的线程离开临界区后,必须释放锁,其他线程方可竞争
锁

操作流程

  1. 定义锁资源(需被多个线程访问,通常为全局变量或参数传递)
  2. 初始化锁资源(静态/动态初始化)
  3. 上锁(进入临界区前,提供阻塞属性)
    • 当任务需要访问临界资源时应该对他进行上锁操作
      • 当代码进入临界区之前应当上锁
  4. 解锁(离开临界区后)
    • 当任务不需要继续访问临界资源时应当解锁
      • 当代码理解临界区时应当解锁
  5. 不再使用时销毁锁

核心操作接口

初始化与销毁

// 动态初始化
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)

基本逻辑

  • 设计初衷:解决互斥锁“读写一律互斥”的效率问题
  • 核心机制:
    1. 读操作:共享访问(多个线程可同时持有读锁)
    2. 写操作:排他访问(仅一个线程可持有写锁,与读锁互斥)
  • 适用场景:读操作远多于写操作的场景(如缓存、配置文件访问)
示意图

操作流程

  1. 定义读写锁资源(变量)
  2. 初始化读写锁(静态/动态)
  3. 上锁(读锁/写锁,根据操作类型选择)
    • 读锁 (共享的,只要没有其他的写锁存在可以允许读锁并发)
    • 写锁 (互斥的,只要有任何的读锁或写锁存在都无法上锁)
  4. 解锁(操作完成后释放)
  5. 不再使用时销毁

核心操作接口

初始化与销毁

// 动态初始化
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);
  • pref参数选项:

    选项 特点 适用场景
    PTHREAD_RWLOCK_PREFER_READER_NP 读优先(默认),允许递归读锁 读操作极少阻塞的场景
    PTHREAD_RWLOCK_PREFER_WRITER_NP 写优先(已弃用),可能造成死锁 不推荐使用
    PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP 写优先,禁止递归读锁 避免写饥饿,推荐使用
  • 核心效果:写锁等待时,不再分配新的读锁,确保写锁能及时获取

示例代码

#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个必要条件

  1. 互斥条件:资源只能被一个线程独占
  2. 请求与保持条件:线程持有部分资源,同时请求其他资源
  3. 不可剥夺条件:资源只能由持有线程主动释放,无法强制剥夺
  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(异步取消,立即响应)
  • 取消点:线程可能响应取消的函数(如sleeppthread_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_pushpthread_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(&not_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(&not_empty); // 唤醒消费者(队列非空)

        sleep(1);
    }
}

// 消费者线程:从队列提取数据
void *consumer(void *arg) {
    while (1) {
        pthread_mutex_lock(&mutex);

        // 队列为空时,等待"非空"条件
        while (front == rear) {
            printf("队列为空,消费者等待...\n");
            pthread_cond_wait(&not_empty, &mutex);
        }

        // 消费数据
        int data = queue[front];
        front = (front + 1) % QUEUE_SIZE;
        printf("消费者消费:%d\n", data);

        pthread_mutex_unlock(&mutex);
        pthread_cond_signal(&not_full); // 唤醒生产者(队列非满)

        sleep(2);
    }
}

int main() {
    // 初始化
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&not_full, NULL);
    pthread_cond_init(&not_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(&not_full);
    pthread_cond_destroy(&not_empty);

    return 0;
}
posted @ 2025-12-03 08:26  Jaklin  阅读(55)  评论(0)    收藏  举报