解码线程同步与互斥

线程同步与互斥概述

线程是进程内的执行单元,同一进程的所有线程共享进程资源。线程并发执行时会出现资源争抢问题,导致共享数据不一致,需通过同步互斥机制解决。

  • 同步:控制线程执行顺序,让线程按预定次序执行。
  • 互斥:禁止线程同时访问临界资源,确保同一时间只有一个线程操作资源。

image

互斥锁(Mutex)

定义

互斥锁是实现互斥的核心机制,本质是二值信号量(值为 0 或 1),用于保护临界资源,避免多线程同时访问。

image

核心函数

初始化互斥锁

#include <pthread.h>
// 动态初始化:通过函数初始化
/*
 * @brief 初始化互斥锁对象
 * @param mutex:指向互斥锁对象的指针(需提前定义pthread_mutex_t类型变量)
 * @param attr:互斥锁属性,NULL表示使用默认属性(推荐)
 * @return 成功返回0,失败返回非0错误码
 * @note 未初始化的互斥锁不能使用;不可重复初始化已初始化的互斥锁
 */
int pthread_mutex_init(pthread_mutex_t *restrict mutex, const pthread_mutexattr_t *restrict attr);

// 静态初始化:直接赋值宏定义
/*
 * @brief 静态初始化互斥锁,效果等同于动态初始化(attr=NULL)
 * @note 无需调用pthread_mutex_init,直接定义变量并赋值即可
 *       无错误检查,适用于全局或静态变量
 */
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

上锁与解锁

// 上锁:阻塞等待,直到获取锁
/*
 * @brief 申请获取互斥锁,保护临界资源
 * @param mutex:已初始化的互斥锁对象指针
 * @return 成功返回0,失败返回非0错误码
 * @note 若锁已被其他线程持有,当前线程阻塞;同一线程不可重复上锁(会导致死锁)
 */
int pthread_mutex_lock(pthread_mutex_t *mutex);

// 尝试上锁:非阻塞,获取失败立即返回
/*
 * @brief 尝试获取互斥锁,不阻塞
 * @param mutex:已初始化的互斥锁对象指针
 * @return 成功返回0,失败返回EAGAIN(锁已被持有)
 * @note 适用于不希望线程长时间阻塞的场景
 */
int pthread_mutex_trylock(pthread_mutex_t *mutex);

/**
 * 带超时机制的互斥锁加锁函数(POSIX标准接口,用于避免线程永久阻塞)
 * @brief 尝试获取互斥锁所有权,若锁已被其他线程持有,
 *        则阻塞等待至指定绝对时间;超时仍未获取则返回错误,
 *        是预防死锁的核心接口之一
 * @param mutex 指向pthread_mutex_t类型互斥锁对象的指针,
 *              需提前通过pthread_mutex_init或PTHREAD_MUTEX_INITIALIZER初始化;
 *              传入未初始化/已销毁的互斥锁会导致未定义行为
 * @param abstime 指向struct timespec类型的绝对时间结构体指针,
 *                指定最晚获取锁的时间点;
 *                结构体成员tv_sec为秒(整数部分),
 *                tv_nsec为纳秒(0~999,999,999,小数部分),
 *                需基于当前系统时间计算(非相对时间)
 * @return int 返回值含义:
 *              - 0:成功获取互斥锁,当前线程成为锁的唯一持有者
 *              - ETIMEDOUT:超时错误,在abstime指定时间前未获取到锁,线程解除阻塞
 *              - EINVAL:参数无效,可能是mutex未初始化、
 *                        abstime的tv_nsec超出合法范围(<0或>999,999,999)
 *              - EDEADLK:死锁风险,当前线程已持有该互斥锁(重复加锁),
 *                        或根据锁属性检测到循环等待
 *              - EBUSY:锁已被其他线程持有但未超时(部分实现返回,
 *                       优先级低于ETIMEDOUT处理)
 * @note 时间类型注意:abstime是绝对时间(如“2024-10-01 12:34:56.123456789”),
 *                        需通过clock_gettime(CLOCK_REALTIME, &abstime)获取当前时间后,
 *                        叠加超时时长(如abstime.tv_sec += 3表示3秒后超时),
 *                        不可直接设为相对时长(如tv_sec=3)
 *       时钟选择:推荐使用CLOCK_REALTIME(与系统时间同步),
 *                    若需避免系统时间调整影响,可使用CLOCK_MONOTONIC(单调递增时钟,
 *                    从系统启动计时),需确保系统支持该时钟类型
 *       编译链接:使用该函数需链接pthread库,
 *                    编译命令需加“-pthread”选项(如gcc demo.c -o demo -pthread),
 *                    否则会报“未定义引用”错误
 *       资源清理:若线程等待锁前已持有其他资源(如另一个锁、动态内存),
 *                    超时后需主动释放,避免资源泄漏
 *       锁属性兼容:支持默认锁、递归锁、错误检查锁,
 *                    但递归锁场景下重复调用会返回EDEADLK(非超时),
 *                    需规避重复加锁逻辑
 */
int pthread_mutex_timedlock(pthread_mutex_t *restrict mutex, 
                            const struct timespec *restrict abstime);

// 解锁:释放锁
/*
 * @brief 释放已获取的互斥锁,允许其他线程竞争
 * @param mutex:已上锁的互斥锁对象指针
 * @return 成功返回0,失败返回非0错误码
 * @note 只能释放当前线程持有的锁;解锁未上锁的锁会导致未定义行为
 */
int pthread_mutex_unlock(pthread_mutex_t *mutex);

销毁互斥锁

/*
 * @brief 销毁互斥锁,释放相关资源
 * @param mutex:已初始化的互斥锁对象指针
 * @return 成功返回0,失败返回非0错误码
 * @note 只能销毁未上锁的互斥锁;销毁后可重新初始化使用
 */
int pthread_mutex_destroy(pthread_mutex_t *mutex);

互斥锁实现线程互斥访问示例

#include <pthread.h>
#include <stdio.h>
// 全局临界资源(主线程和子线程共享)
int global_val = 0;
// 静态初始化互斥锁
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

// 子线程任务函数
void *child_task(void *arg) {
    while (1) {
        // 访问临界资源前上锁
        pthread_mutex_lock(&mutex);
        /* 临界区:操作共享资源 */
        global_val = 10;
        printf("子线程:global_val = %d\n", global_val);
        /* 临界区结束 */
        // 操作完成后解锁
        pthread_mutex_unlock(&mutex);
        sleep(1); // 模拟其他操作
    }
    return NULL;
}

int main() {
    pthread_t tid;
    // 创建子线程
    pthread_create(&tid, NULL, child_task, NULL);

    while (1) {
        // 访问临界资源前上锁
        pthread_mutex_lock(&mutex);
        /* 临界区:操作共享资源 */
        global_val = 20;
        printf("主线程:global_val = %d\n", global_val);
        /* 临界区结束 */
        // 操作完成后解锁
        pthread_mutex_unlock(&mutex);
        sleep(1); // 模拟其他操作
    }

    // 销毁互斥锁(实际不会执行,因主线程和子线程均为死循环)
    pthread_mutex_destroy(&mutex);
    return 0;
}

超时机制上锁示例

#include <pthread.h>
#include <time.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

// 全局互斥锁:保护共享资源,静态初始化
pthread_mutex_t g_shared_mutex = PTHREAD_MUTEX_INITIALIZER;
// 共享资源:模拟需要互斥访问的数据
int g_shared_data = 0;

/**
 * @brief 子线程函数:模拟长时间持有互斥锁,占用共享资源
 * @param arg 线程参数(本示例未使用,设为NULL)
 * @return 线程退出返回值(NULL)
 */
void* hold_lock_thread(void* arg) {
    printf("[子线程] 尝试获取互斥锁...\n");
    
    // 普通加锁(无超时,持有锁5秒)
    int ret = pthread_mutex_lock(&g_shared_mutex);
    if (ret != 0) {
        perror("[子线程] pthread_mutex_lock 失败");
        pthread_exit(NULL);
    }
    
    printf("[子线程] 成功获取互斥锁,开始占用共享资源\n");
    // 模拟耗时操作:持有锁5秒,期间修改共享资源
    g_shared_data = 100; // 修改共享资源
    printf("[子线程] 当前共享资源值:%d,将持有锁5秒\n", g_shared_data);
    sleep(5); // 关键:持有锁5秒,制造主线程超时场景
    
    // 释放锁
    pthread_mutex_unlock(&g_shared_mutex);
    printf("[子线程] 释放互斥锁,退出子线程\n");
    
    pthread_exit(NULL);
}

/**
 * @brief 主线程函数:使用pthread_mutex_timedlock尝试超时获取互斥锁
 * @return 程序退出码(0为正常)
 */
int main() {
    pthread_t hold_tid;
    int ret;
    struct timespec g_timeout; // 超时时间结构体(绝对时间)

    // 创建子线程:子线程会先获取锁并持有5秒
    ret = pthread_create(&hold_tid, NULL, hold_lock_thread, NULL);
    if (ret != 0) {
        perror("pthread_create 失败");
        exit(EXIT_FAILURE);
    }
    // 延迟1秒:确保子线程先获取到锁,制造主线程等待场景
    sleep(1);

    // 设置超时时间:当前系统时间 + 3秒(绝对时间)
    // CLOCK_REALTIME:使用系统实时时间(与date命令同步)
    ret = clock_gettime(CLOCK_REALTIME, &g_timeout);
    if (ret == -1) {
        perror("clock_gettime 失败");
        // 清理:取消子线程并销毁锁
        pthread_cancel(hold_tid);
        pthread_mutex_destroy(&g_shared_mutex);
        exit(EXIT_FAILURE);
    }
    g_timeout.tv_sec += 3;  // 秒数+3:超时时间设为3秒后
    g_timeout.tv_nsec = 0;  // 纳秒部分设为0(简化示例,也可设为500000000表示3.5秒)

    // 调用pthread_mutex_timedlock:超时获取互斥锁
    printf("\n[主线程] 开始尝试获取互斥锁,超时时间:3秒\n");
    ret = pthread_mutex_timedlock(&g_shared_mutex, &g_timeout);

    // 根据返回值处理不同场景
    switch (ret) {
        case 0:
            // 成功获取锁:操作共享资源
            printf("[主线程] 成功获取互斥锁!\n");
            g_shared_data += 50; // 修改共享资源
            printf("[主线程] 共享资源更新后的值:%d\n", g_shared_data);
            // 释放锁(必须释放,避免其他线程阻塞)
            pthread_mutex_unlock(&g_shared_mutex);
            printf("[主线程] 释放互斥锁\n");
            break;

        case ETIMEDOUT:
            // 超时场景:3秒内未获取到锁,避免永久阻塞
            printf("[主线程] 获取互斥锁超时(已等待3秒),放弃等待\n");
            // 可在此处添加降级逻辑(如重试、提示用户等)
            break;

        case EINVAL:
            // 参数无效:如锁未初始化、超时时间格式错误
            printf("[主线程] 参数无效:互斥锁未初始化或超时时间格式错误\n");
            break;

        case EDEADLK:
            // 死锁风险:当前线程已持有该锁(重复加锁)或循环等待
            printf("[主线程] 检测到死锁风险:可能重复加锁或循环等待\n");
            break;

        default:
            // 其他错误
            printf("[主线程] 获取互斥锁失败,错误码:%d,错误信息:%s\n", ret, strerror(ret));
            break;
    }

    // 资源清理:等待子线程退出,销毁互斥锁
    printf("\n[主线程] 等待子线程退出...\n");
    ret = pthread_join(hold_tid, NULL);
    if (ret != 0) {
        perror("pthread_join 失败");
    }

    // 销毁互斥锁(必须在所有线程释放锁后调用)
    ret = pthread_mutex_destroy(&g_shared_mutex);
    if (ret != 0) {
        perror("pthread_mutex_destroy 失败");
        exit(EXIT_FAILURE);
    }

    printf("[主线程] 资源清理完成,程序正常退出\n");
    return 0;
}

colck_gettime()

#include <time.h>
/**
 * 高精度系统时间获取函数(POSIX标准接口,支持纳秒级精度)
 * @brief 用于获取指定类型系统时钟的当前时间,提供秒+纳秒级高精度时间数据,
 *        核心用于设置绝对超时时间(如pthread_mutex_timedlock)、计算时间间隔等场景
 * @param clk_id 时钟类型ID,指定要获取的系统时钟类型,常用取值及说明:
 *              - CLOCK_REALTIME:系统实时时间(墙上时间),与date命令同步,
 *                                可被手动修改(如date -s),适用于需与系统时间对齐的场景;
 *              - CLOCK_MONOTONIC:单调递增时钟,从系统启动时开始计时,
 *                                不受系统时间修改影响(不会回退),适用于时间间隔计算;
 *              - CLOCK_PROCESS_CPUTIME_ID:当前进程占用CPU的累计时间,
 *                                仅统计进程实际运行在CPU上的时间(不含阻塞时间);
 *              - CLOCK_THREAD_CPUTIME_ID:当前线程占用CPU的累计时间,
 *                                仅统计线程实际运行在CPU上的时间(不含阻塞时间);
 *              - CLOCK_REALTIME_COARSE:低精度实时时间,效率高于CLOCK_REALTIME,
 *                                适用于对精度要求不高但需快速获取时间的场景
 * @param tp 指向struct timespec类型的指针,用于存储获取到的高精度时间(输出参数),
 *           结构体定义及成员说明:
 *           struct timespec {
 *               time_t tv_sec;   // 时间的秒数部分(整数,如1696123456秒)
 *               long   tv_nsec;  // 时间的纳秒部分(小数,范围:0 ~ 999,999,999)
 *           };
 *           示例:时间1696123456.789012345秒 → tv_sec=1696123456,tv_nsec=789012345
 * @return int 返回值含义:
 *              - 0:成功获取时间,tp指向的结构体已填充有效数据;
 *              - -1:获取失败,错误原因存储在errno中,常见错误码:
 *                    EINVAL:clk_id指定的时钟类型不支持;
 *                    EFAULT:tp指针为NULL或指向非法内存地址
 * @note tv_nsec范围限制:必须在0~999,999,999之间,不可超过1秒(10^9纳秒),
 *                        处理时需避免溢出(如计算后tv_nsec≥1e9,需转换为tv_sec+1);
 *       时钟类型选择:计算时间间隔优先用CLOCK_MONOTONIC(避免系统时间修改影响),
 *                        需与外部时间对齐用CLOCK_REALTIME;
 *       编译兼容性:大部分Linux系统默认支持,部分嵌入式系统可能需链接实时库(编译加-lrt);
 *       tp参数要求:必须提前分配内存(栈/堆均可),不可为NULL,否则返回EFAULT错误;
 *       精度说明:实际精度取决于系统硬件和内核,不一定能达到理论上的纳秒级(通常为微秒级)
 */
int clock_gettime(clockid_t clk_id, struct timespec *tp);

注意事项

  • 上锁后必须解锁,避免其他线程永久阻塞。
  • 临界区代码应尽量简短,减少锁持有时间。
  • 禁止同一线程对同一互斥锁重复上锁(死锁风险)。

读写锁(RWLock)

定义

读写锁是优化版互斥锁,区分读操作和写操作:读操作可并发执行(共享锁),写操作互斥执行(排他锁),适用于 “读多写少” 场景,提升程序效率。

核心函数

初始化读写锁

#include <pthread.h>
// 动态初始化
/*
 * @brief 初始化读写锁对象
 * @param rwlock:指向读写锁对象的指针
 * @param attr:读写锁属性,NULL表示默认属性
 * @return 成功返回0,失败返回非0错误码
 * @note 未初始化的读写锁不可使用;不可重复初始化
 */
int pthread_rwlock_init(pthread_rwlock_t *restrict rwlock, const pthread_rwlockattr_t *restrict attr);

// 静态初始化
/*
 * @brief 静态初始化读写锁,效果等同于动态初始化(attr=NULL)
 * @note 适用于全局或静态变量,无错误检查
 */
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

加锁与解锁

// 读锁:共享锁,多个线程可同时获取
/*
 * @brief 申请读锁,用于读取共享资源
 * @param rwlock:已初始化的读写锁对象指针
 * @return 成功返回0,失败返回非0错误码
 * @note 若已有写锁持有,当前线程阻塞;已有读锁时,新线程可直接获取读锁
 */
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);

// 写锁:排他锁,同一时间只能有一个线程获取
/*
 * @brief 申请写锁,用于修改共享资源
 * @param rwlock:已初始化的读写锁对象指针
 * @return 成功返回0,失败返回非0错误码
 * @note 若已有读锁或写锁持有,当前线程阻塞;写锁优先级通常高于读锁
 */
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

// 尝试获取读锁:非阻塞
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
// 尝试获取写锁:非阻塞
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
//带超时机制的读锁加锁函数(POSIX标准接口,用于避免线程永久阻塞)
int pthread_rwlock_timedrdlock(pthread_rwlock_t *restrict rwlock,
           const struct timespec *restrict abstime);
//带超时机制的写锁加锁函数(POSIX标准接口,用于避免线程永久阻塞)           
int pthread_rwlock_timedwrlock(pthread_rwlock_t *restrict rwlock,
           const struct timespec *restrict abstime);

// 解锁:释放读锁或写锁
/*
 * @brief 释放已获取的读锁或写锁
 * @param rwlock:已加锁的读写锁对象指针
 * @return 成功返回0,失败返回非0错误码
 * @note 读锁解锁:若释放后无其他读锁,锁变为未锁定状态;
 *       写锁解锁:直接释放锁,允许其他线程竞争
 */
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);

销毁读写锁

/*
 * @brief 销毁读写锁,释放相关资源
 * @param rwlock:已初始化的读写锁对象指针
 * @return 成功返回0,失败返回非0错误码
 * @note 只能销毁未上锁的读写锁;销毁后可重新初始化
 */
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);

读写锁实现读写分离示例

#include <pthread.h>
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
// 共享资源:存储时间的文本内容
char time_buf[64] = {0};
// 静态初始化读写锁
pthread_rwlock_t rwlock = PTHREAD_RWLOCK_INITIALIZER;

// 子线程B:读取系统时间(hh:mm:ss)
void *task_B(void *arg) {
    while (1) {
        // 获取读锁
        pthread_rwlock_rdlock(&rwlock);
        /* 读临界区:读取时间并提取时分秒 */
        char hhmmss[16] = {0};
        strncpy(hhmmss, time_buf, 8); // 假设time_buf格式为"hh:mm:ss yyyy-mm-dd"
        printf("线程B(时间):%s\n", hhmmss);
        /* 读临界区结束 */
        // 释放读锁
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
    return NULL;
}

// 子线程C:读取系统日期(yyyy-mm-dd)
void *task_C(void *arg) {
    while (1) {
        // 获取读锁
        pthread_rwlock_rdlock(&rwlock);
        /* 读临界区:读取时间并提取日期 */
        char yyyymmdd[16] = {0};
        strncpy(yyyymmdd, time_buf + 9, 10);
        printf("线程C(日期):%s\n", yyyymmdd);
        /* 读临界区结束 */
        // 释放读锁
        pthread_rwlock_unlock(&rwlock);
        sleep(1);
    }
    return NULL;
}

// 主线程:每隔5秒写入系统时间到共享缓冲区
int main() {
    pthread_t tid_B, tid_C;
    // 初始化读写锁(静态初始化可省略此步骤)
    // pthread_rwlock_init(&rwlock, NULL);

    // 创建子线程
    pthread_create(&tid_B, NULL, task_B, NULL);
    pthread_create(&tid_C, NULL, task_C, NULL);

    while (1) {
        sleep(5); // 每隔5秒写入一次
        // 获取写锁
        pthread_rwlock_wrlock(&rwlock);
        /* 写临界区:获取系统时间并写入缓冲区 */
        time_t now = time(NULL);
        struct tm *tm_now = localtime(&now);
        strftime(time_buf, sizeof(time_buf), "%H:%M:%S %Y-%m-%d", tm_now);
        printf("主线程(写入):%s\n", time_buf);
        /* 写临界区结束 */
        // 释放写锁
        pthread_rwlock_unlock(&rwlock);
    }

    // 销毁读写锁(实际不会执行)
    pthread_rwlock_destroy(&rwlock);
    return 0;
}

实现写优先策略示例

函数解析

/**
 * 初始化读写锁属性对象(POSIX标准接口)
 * @brief 为读写锁属性分配资源并初始化默认值,是设置读写锁特性(如写优先)的前提
 * @param attr 指向pthread_rwlockattr_t类型属性对象的指针,需提前定义(栈/全局变量均可)
 * @return int 返回值含义:
 *              - 0:初始化成功,属性对象处于可用状态;
 *              - 非0:初始化失败(如系统资源不足),错误码通过返回值返回
 * @note 必须在设置属性(如写优先)和初始化读写锁前调用;
 *       未初始化的属性对象不可用于后续接口(如pthread_rwlockattr_setkind_np);
 *       初始化后的属性对象默认值:读优先(PTHREAD_RWLOCK_DEFAULT_NP)、进程内共享;
 *       用完后需调用pthread_rwlockattr_destroy销毁,避免资源泄漏
 */
int pthread_rwlockattr_init(pthread_rwlockattr_t *attr);

/**
 * 设置读写锁的调度策略(GNU扩展接口,Linux专属)
 * @brief 核心接口:指定读写锁的优先级策略(读优先/写优先),实现写优先的关键
 * @param attr 指向已初始化的pthread_rwlockattr_t属性对象的指针
 * @param pref 优先级策略取值,仅支持3种(GNU扩展定义):
 *              - PTHREAD_RWLOCK_PREFER_READER_NP:读优先(默认),新读锁可插队写锁等待队列,可能导致写饥饿;
 *              - PTHREAD_RWLOCK_PREFER_WRITER_NP:写优先,写锁申请后阻塞新读锁,确保写线程优先执行;
 *              - PTHREAD_RWLOCK_DEFAULT_NP:等同于读优先,兼容早期版本
 * @return int 返回值含义:
 *              - 0:属性设置成功,策略已生效;
 *              - EINVAL:参数无效(如attr未初始化、pref取值非法);
 *              - 其他非0:系统不支持该策略(极少出现)
 * @note 兼容性:仅Linux系统支持(GNU C库扩展,NP=Non-Portable),其他系统(macOS/FreeBSD)需用对应平台接口;
 *       调用时机:必须在pthread_rwlock_init(初始化读写锁)之前,否则属性对已创建的锁无效;
 *       写优先特性:写锁等待时,新读锁无法插队,仅允许当前持有读锁的线程释放后,写锁获取;
 *       不可重复设置:多次调用以最后一次设置的策略为准
 */
int pthread_rwlockattr_setkind_np(pthread_rwlockattr_t *attr, int pref);

/**
 * 销毁读写锁属性对象(POSIX标准接口)
 * @brief 释放属性对象占用的系统资源,属性对象使用完毕后的清理操作
 * @param attr 指向已初始化的pthread_rwlockattr_t属性对象的指针
 * @return int 返回值含义:
 *              - 0:销毁成功,属性对象不可再使用;
 *              - EINVAL:参数无效(如attr未初始化、已销毁)
 * @note 1. 调用时机:读写锁初始化(pthread_rwlock_init)完成后即可调用,属性对象仅用于锁初始化;
 *       2. 不可重复销毁:对已销毁的属性对象再次调用会返回错误;
 *       3. 资源释放:销毁后属性对象占用的内存等资源被释放,避免内存泄漏
 */
int pthread_rwlockattr_destroy(pthread_rwlockattr_t *attr);

实现

/**
 * 读写锁属性设置:实现写优先策略(GNU扩展,Linux系统适用)
 * @brief 通过设置读写锁属性,让写锁申请时优先于后续读锁获取,避免写线程饥饿
 * 核心原理:当有写锁等待时,新的读锁申请会被阻塞,直到所有等待的写锁完成操作
 * 
 * 关键属性接口:pthread_rwlockattr_setkind_np(NP=Non-Portable,非POSIX标准,GNU扩展)
 * 属性取值说明:
 * - PTHREAD_RWLOCK_PREFER_READER_NP:默认读优先(新读锁可插队写锁等待队列)
 * - PTHREAD_RWLOCK_PREFER_WRITER_NP:写优先(写锁等待时,阻塞新读锁申请)
 * - PTHREAD_RWLOCK_DEFAULT_NP:等同于读优先
 */
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>

// 全局读写锁(将通过写优先属性初始化)
pthread_rwlock_t g_rwlock;
// 共享资源:模拟读多写少的数据源
int g_shared_data = 0;
// 控制线程退出的标志
int g_exit_flag = 0;

/**
 * @brief 读线程函数:循环获取读锁,读取共享资源
 * @param arg 线程ID(用于打印区分)
 * @return NULL
 */
void* reader_thread(void* arg) {
    int tid = *(int*)arg;
    free(arg); // 释放传入的动态分配ID

    while (!g_exit_flag) {
        // 申请读锁(写优先模式下,若有写锁等待,此处会阻塞)
        int ret = pthread_rwlock_rdlock(&g_rwlock);
        if (ret != 0) {
            perror("pthread_rwlock_rdlock failed");
            break;
        }

        // 读临界区:读取共享资源(模拟耗时读操作)
        printf("[读线程%d] 成功获取读锁,共享数据:%d\n", tid, g_shared_data);
        usleep(500000); // 模拟读操作耗时0.5秒

        // 释放读锁
        pthread_rwlock_unlock(&g_rwlock);
        usleep(100000); // 读线程间隔0.1秒再次申请读锁(制造高并发读场景)
    }

    printf("[读线程%d] 退出\n", tid);
    pthread_exit(NULL);
}

/**
 * @brief 写线程函数:延迟后申请写锁,修改共享资源
 * @param arg 线程ID(用于打印区分)
 * @return NULL
 */
void* writer_thread(void* arg) {
    int tid = *(int*)arg;
    free(arg); // 释放传入的动态分配ID

    // 延迟2秒启动:让读线程先占据读锁,模拟写锁等待场景
    sleep(2);
    printf("\n[写线程%d] 开始申请写锁...\n", tid);

    // 申请写锁(写优先模式下,会阻塞直到当前读锁释放,且后续读锁无法插队)
    int ret = pthread_rwlock_wrlock(&g_rwlock);
    if (ret != 0) {
        perror("pthread_rwlock_wrlock failed");
        pthread_exit(NULL);
    }

    // 写临界区:修改共享资源(模拟耗时写操作)
    g_shared_data += 10;
    printf("[写线程%d] 成功获取写锁,共享数据更新为:%d\n", tid, g_shared_data);
    usleep(1000000); // 模拟写操作耗时1秒

    // 释放写锁
    pthread_rwlock_unlock(&g_rwlock);
    printf("[写线程%d] 释放写锁\n\n", tid);

    // 写操作完成后,设置退出标志,让读线程退出
    g_exit_flag = 1;
    pthread_exit(NULL);
}

int main() {
    pthread_t readers[3]; // 3个读线程(模拟高并发读)
    pthread_t writer;     // 1个写线程(模拟写请求)
    pthread_rwlockattr_t rwlock_attr; // 读写锁属性对象
    int ret, i;

    // 初始化读写锁属性
    ret = pthread_rwlockattr_init(&rwlock_attr);
    if (ret != 0) {
        perror("pthread_rwlockattr_init failed");
        exit(EXIT_FAILURE);
    }

    // 设置写优先属性(核心步骤)
    // 注意:PTHREAD_RWLOCK_PREFER_WRITER_NP是GNU扩展,仅Linux系统支持
    ret = pthread_rwlockattr_setkind_np(&rwlock_attr, PTHREAD_RWLOCK_PREFER_WRITER_NP);
    if (ret != 0) {
        perror("pthread_rwlockattr_setkind_np failed");
        pthread_rwlockattr_destroy(&rwlock_attr);
        exit(EXIT_FAILURE);
    }

    // 用写优先属性初始化读写锁
    ret = pthread_rwlock_init(&g_rwlock, &rwlock_attr);
    if (ret != 0) {
        perror("pthread_rwlock_init failed");
        pthread_rwlockattr_destroy(&rwlock_attr);
        exit(EXIT_FAILURE);
    }

    // 销毁属性对象(初始化读写锁后,属性对象可释放)
    pthread_rwlockattr_destroy(&rwlock_attr);

    // 创建3个读线程
    for (i = 0; i < 3; i++) {
        int* tid = malloc(sizeof(int));
        *tid = i + 1;
        ret = pthread_create(&readers[i], NULL, reader_thread, tid);
        if (ret != 0) {
            perror("pthread_create reader failed");
            g_exit_flag = 1;
            exit(EXIT_FAILURE);
        }
    }

    // 创建1个写线程
    int* writer_tid = malloc(sizeof(int));
    *writer_tid = 1;
    ret = pthread_create(&writer, NULL, writer_thread, writer_tid);
    if (ret != 0) {
        perror("pthread_create writer failed");
        g_exit_flag = 1;
        exit(EXIT_FAILURE);
    }

    // 等待所有线程退出
    for (i = 0; i < 3; i++) {
        pthread_join(readers[i], NULL);
    }
    pthread_join(writer, NULL);

    // 销毁读写锁
    pthread_rwlock_destroy(&g_rwlock);

    printf("主线程:所有线程退出,资源清理完成\n");
    return 0;
}

注意事项

  • 读锁共享:多个线程可同时持有读锁,互不阻塞。
  • 写锁排他:写锁与读锁、写锁与写锁均互斥。
  • 适用场景:读操作频率远高于写操作(如配置文件读取、数据查询)。

POSIX 信号量

定义

信号量是用于同步的计数器,描述共享资源的可用数量,支持线程间或进程间同步。POSIX 信号量分为匿名信号量(线程间)和具名信号量(进程间)。

匿名信号量(线程间同步)

核心函数

#include <semaphore.h>
// 初始化匿名信号量
/*
 * @brief 初始化匿名信号量
 * @param sem:指向信号量对象的指针
 * @param pshared:0表示线程间共享(匿名信号量默认用法),非0表示进程间共享(需配合共享内存)
 * @param value:信号量初始值(资源可用数量)
 * @return 成功返回0,失败返回-1(并设置errno)
 * @note 匿名信号量需定义为全局变量或堆变量(确保所有线程可见);不可重复初始化
 */
int sem_init(sem_t *sem, int pshared, unsigned int value);

// P操作:申请资源(信号量减1)
/*
 * @brief 申请资源,信号量值减1
 * @param sem:已初始化的信号量对象指针
 * @return 成功返回0,失败返回-1
 * @note 若信号量值为0,当前线程阻塞,直到信号量值>0
 */
int sem_wait(sem_t *sem);

// 尝试P操作:非阻塞
/*
 * @brief 尝试申请资源,信号量值减1
 * @param sem:已初始化的信号量对象指针
 * @return 成功返回0,失败返回-1(errno=EAGAIN表示资源不可用)
 */
int sem_trywait(sem_t *sem);

/**
 * 带超时的信号量P操作函数(POSIX标准接口)
 * @brief 尝试获取信号量资源(信号量值减1),若资源不可用(值为0)则阻塞等待,
 *        超时仍未获取则自动解除阻塞,避免线程永久等待,支持线程间/进程间同步
 * @param sem 指向已初始化的sem_t类型信号量的指针,
 *            匿名信号量通过sem_init初始化(线程间共享),
 *            具名信号量通过sem_open创建(进程间共享),未初始化则返回EINVAL
 * @param abstime 指向struct timespec类型的绝对超时时间结构体指针,
 *                指定“最晚获取资源的时间”,结构体成员:
 *                - tv_sec:秒(整数部分)
 *                - tv_nsec:纳秒(0~999,999,999,小数部分)
 *                需基于当前系统时间计算(如当前时间+3秒),非相对时间
 * @return int 返回值含义:
 *              - 0:成功获取信号量,信号量值减1;
 *              - ETIMEDOUT:超时错误,在abstime指定时间前未获取到资源,线程解除阻塞;
 *              - EINVAL:参数无效(sem未初始化、abstime的tv_nsec超出合法范围);
 *              - EDEADLK:检测到死锁风险(极少出现,部分系统支持);
 *              - EINTR:等待过程中被信号中断(需根据业务决定是否重试);
 *              - EBUSY:信号量正被其他进程/线程占用(未超时,部分实现返回)
 * @note 信号量特性:适用于同步(初始值0)或互斥(初始值1)场景,
 *                        调用成功后信号量值原子减1,释放时需调用sem_post(值加1);
 *       绝对时间设置:需通过clock_gettime(CLOCK_REALTIME, &abstime)获取当前时间,
 *                        叠加超时时长(如abstime.tv_sec += 3表示3秒后超时),
 *                        不可直接设为相对时长(如tv_sec=3);
 *       编译链接:使用需链接pthread库(部分系统需额外加-lrt),编译命令加“-pthread”;
 *       信号量类型兼容:支持匿名信号量(线程间)和具名信号量(进程间),
 *                        初始化时通过sem_init的pshared参数区分(0=线程间,非0=进程间);
 *       资源释放:若超时后不再需要信号量,需调用sem_destroy(匿名)或sem_close+sem_unlink(具名)
 */
int pthread_semtimedwait(sem_t *sem, const struct timespec *abstime);

// V操作:释放资源(信号量加1)
 /*
 * @brief 释放资源,信号量值加1
 * @param sem:已初始化的信号量对象指针
 * @return 成功返回0,失败返回-1
 * @note 若有线程阻塞在sem_wait,会唤醒一个线程
 */
int sem_post(sem_t *sem);

// 销毁匿名信号量
/*
 * @brief 销毁匿名信号量,释放资源
 * @param sem:已初始化的信号量对象指针
 * @return 成功返回0,失败返回-1
 * @note 只能销毁未被使用的信号量;销毁后不可再使用
 */
int sem_destroy(sem_t *sem);

代码示例:匿名信号量实现线程同步

#include <pthread.h>
#include <semaphore.h>
#include <stdio.h>
#include <string.h>
// 共享缓冲区:存储键盘输入
char buf[128] = {0};
// 匿名信号量:初始值0(子线程等待主线程输入)
sem_t sem;

// 子线程:等待主线程输入后输出内容
void *child_task(void *arg) {
    while (1) {
        // P操作:等待主线程释放资源(输入完成)
        sem_wait(&sem);
        /* 临界区:输出缓冲区内容 */
        printf("子线程输出:%s\n", buf);
        memset(buf, 0, sizeof(buf)); // 清空缓冲区
        /* 临界区结束 */
        // 此处可根据需求决定是否执行V操作(本示例无需重复申请)
    }
    return NULL;
}

int main() {
    pthread_t tid;
    // 初始化匿名信号量:线程间共享,初始值0
    sem_init(&sem, 0, 0);

    // 创建子线程
    pthread_create(&tid, NULL, child_task, NULL);

    // 主线程:读取键盘输入并通知子线程
    while (1) {
        printf("请输入字符串:");
        scanf("%s", buf);
        // V操作:释放资源(通知子线程可以读取)
        sem_post(&sem);
    }

    // 销毁信号量(实际不会执行)
    sem_destroy(&sem);
    return 0;
}

具名信号量(进程间同步)

核心函数

#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
// 创建/打开具名信号量
/*
 * @brief 创建或打开具名信号量
 * @param name:信号量名称(格式为"/sem_name",需以/开头)
 * @param oflag:操作标志,O_CREAT表示创建,O_EXCL表示排他创建(避免重复)
 * @param mode:信号量权限(如0666表示所有用户可读可写),O_CREAT时必须指定
 * @param value:信号量初始值,O_CREAT时必须指定
 * @return 成功返回信号量指针,失败返回SEM_FAILED(并设置errno)
 * @note 具名信号量存储在/dev/shm目录下,进程结束后不会自动删除
 */
sem_t *sem_open(const char *name, int oflag, mode_t mode, unsigned int value);

// P操作、尝试P操作、V操作:与匿名信号量相同
int sem_wait(sem_t *sem);
int sem_trywait(sem_t *sem);
int pthread_semtimedwait(sem_t *sem, const struct timespec *abstime);
int sem_post(sem_t *sem);

// 关闭具名信号量
int sem_close(sem_t *sem);
/*
 * @brief 关闭具名信号量,释放当前进程的信号量句柄
 * @param sem:sem_open返回的信号量指针
 * @return 成功返回0,失败返回-1
 * @note 关闭后信号量仍存在,其他进程可继续使用
 */

// 删除具名信号量
int sem_unlink(const char *name);
/*
 * @brief 永久删除具名信号量文件
 * @param name:sem_open指定的信号量名称
 * @return 成功返回0,失败返回-1
 * @note 需所有进程关闭信号量后,删除才生效
 */

代码示例:具名信号量实现进程同步(两个程序)

shm_open()

/**
 * 创建/打开POSIX共享内存对象(POSIX标准接口)
 * @brief 用于在文件系统命名空间中创建或打开一个共享内存对象,
 *        返回文件描述符供后续操作(如ftruncate设置大小、mmap映射),
 *        是现代进程间共享内存通信的首选接口
 * @param name 共享内存对象的名称(非普通文件路径),必须严格满足:
 *            - 以 '/' 开头(唯一允许的 '/' 位置,如 "/my_shm");
 *            - 路径中不能包含多个 '/'(包括连续 '/' 如 "//my_shm"、多级目录 '/' 如 "/dir/my_shm",均非法);
 *            - 名称主体为单级字符串('/' 后直接跟名称,无子目录结构,如 "/moduleA_shm" 合法,"/dir/my_shm" 非法);
 *            - 属于系统级命名空间,对应 /dev/shm 目录下的虚拟文件(可通过 ls /dev/shm 查看);
 *            - 不同进程通过相同名称可访问同一共享内存,名称长度通常不超过 NAME_MAX(约255字节)
 * @param oflag 打开/创建模式标志,核心取值组合:
 *             - O_RDONLY:只读模式打开已存在的共享内存;
 *             - O_RDWR:读写模式打开/创建共享内存;
 *             - O_CREAT:若共享内存不存在则创建(需配合 mode 参数指定权限);
 *             - O_EXCL:与 O_CREAT 搭配使用,若共享内存已存在则返回 EEXIST(避免覆盖);
 *             - O_TRUNC:打开已存在的共享内存时,将其大小截断为0(谨慎使用,会清空数据)
 * @param mode 共享内存的访问权限(仅 O_CREAT 时有效),格式与文件权限一致:
 *            - 常用值:0666(所有用户可读可写)、0644(所有者读写,其他用户只读);
 *            - 权限受 umask 影响(实际权限 = mode & ~umask),需确保权限满足进程访问需求
 * @return int 返回值含义:
 *              - 非负整数:成功,返回共享内存的文件描述符(fd),用于后续 ftruncate、mmap 等操作;
 *              - -1:失败,错误原因存储在 errno 中,常见错误码:
 *                    EEXIST:O_CREAT + O_EXCL 时,共享内存已存在;
 *                    ENOENT:未指定 O_CREAT,且共享内存不存在;
 *                    EACCES:权限不足(如只读模式打开需写权限,或 /dev/shm 目录无访问权);
 *                    EINVAL:name 格式非法(未以 '/' 开头、含多个 '/'、多级目录等);
 *                    ENOMEM:系统资源不足,无法创建共享内存;
 *                    EROFS:/dev/shm 所在文件系统为只读,无法创建
 * @note 初始大小:创建的共享内存初始大小为 0,必须调用 ftruncate 设置实际大小后,才能通过 mmap 映射;
 *       文件描述符管理:使用完毕后需调用 shm_close(fd) 关闭,避免文件描述符泄漏;
 *       生命周期:共享内存默认随系统重启消失,进程关闭 fd 后不会立即释放,需调用 shm_unlink(name) 永久删除(所有进程关闭 fd 后生效);
 *       兼容性:POSIX 标准接口,支持 Linux、macOS、FreeBSD 等现代系统,部分嵌入式系统可能不支持;
 *       与 System V 共享内存(shmget)的区别:通过名称(而非 key_t)标识、返回文件描述符(兼容 POSIX I/O 接口)、支持动态调整大小(ftruncate)、管理更直观;
 *       编译链接:Linux 系统下部分版本需链接 rt 库(编译加 -lrt),推荐直接加 -pthread 兼容更多场景;
 *       名称冲突避免:可在名称中加入程序标识(如 "/app1_moduleA_shm"),避免与其他程序的共享内存重名
 */
int shm_open(const char *name, int oflag, mode_t mode);

ftruncate(f:是 file(文件)的缩写,truncate:英文原义是 “截断、缩短”,此处扩展为 “调整大小”)

/**
 * 调整文件/共享内存大小函数(POSIX标准接口)
 * @brief 用于修改已打开文件或共享内存对象的大小,核心场景是为共享内存(shm_open创建)
 *        分配实际存储空间,或截断/扩展普通文件,调整后文件偏移量不变
 * @param fd 已打开的文件描述符,需满足:
 *           - 以可写模式打开(如O_RDWR、O_WRONLY),只读模式(O_RDONLY)会返回EINVAL;
 *           - 对应对象支持大小调整(普通文件、共享内存均支持,管道、套接字不支持)
 * @param length 目标大小(单位:字节),取值规则:
 *               - length > 当前大小:文件/共享内存扩展,新增部分填充0;
 *               - length < 当前大小:文件/共享内存截断,超出length的部分数据丢失;
 *               - length = 当前大小:无操作,返回成功
 * @return int 返回值含义:
 *              - 0:大小调整成功;
 *              - -1:调整失败,错误原因存储在errno中,常见错误码:
 *                    EBADF:fd不是有效的文件描述符;
 *                    EINVAL:fd对应对象不支持大小调整,或length为负数,或fd以只读模式打开;
 *                    EROFS:fd对应文件/共享内存位于只读文件系统(如/proc);
 *                    ENOSPC:文件系统剩余空间不足,无法扩展大小
 * @note 共享内存必备操作:通过shm_open创建的共享内存初始大小为0,
 *                        必须调用ftruncate设置大小后,才能通过mmap映射到进程地址空间;
 *       文件偏移量影响:ftruncate不改变文件描述符的当前偏移量(由lseek控制);
 *       普通文件扩展:扩展后的新空间内容为0,即使原文件末尾有数据,新增部分也不会继承;
 *       兼容性:POSIX标准接口,Linux、macOS、FreeBSD等系统均支持;
 *       与truncate区别:truncate接收文件路径,ftruncate接收文件描述符,功能完全一致
 */
int ftruncate(int fd, off_t length);

程序 A(写入 PID 到共享内存)

#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#define SEM_NAME "/my_sem"
#define SHM_NAME "/my_shm"
#define SHM_SIZE sizeof(pid_t)
int main() {
    sem_t *sem;
    int shm_fd;
    pid_t *shm_ptr;

    // 创建/打开具名信号量:初始值0
    sem = sem_open(SEM_NAME, O_CREAT | O_EXCL, 0666, 0);
    if (sem == SEM_FAILED) {
        perror("sem_open");
        return 1;
    }

    // 创建共享内存
    shm_fd = shm_open(SHM_NAME, O_CREAT | O_RDWR, 0666);
    ftruncate(shm_fd, SHM_SIZE);
    shm_ptr = (pid_t *)mmap(NULL, SHM_SIZE, PROT_READ | PROT_WRITE, MAP_SHARED, shm_fd, 0);

    // 写入当前进程PID到共享内存
    *shm_ptr = getpid();
    printf("程序A:PID = %d 已写入共享内存\n", *shm_ptr);

    // V操作:通知程序B可以读取
    sem_post(sem);  sleep(5); // 睡眠5s,让进程B等待信号量释放

    // 等待一段时间后清理资源
    sleep(10);
    sem_close(sem);
    sem_unlink(SEM_NAME);
    munmap(shm_ptr, SHM_SIZE);
    shm_unlink(SHM_NAME);

    return 0;
}

程序 B(读取共享内存中的 PID)

#include <semaphore.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <stdio.h>
#define SEM_NAME "/my_sem"
#define SHM_NAME "/my_shm"
#define SHM_SIZE sizeof(pid_t)
int main() {
    sem_t *sem;
    int shm_fd;
    pid_t *shm_ptr;

    // 打开具名信号量(程序A已创建)
    sem = sem_open(SEM_NAME, 0);
    if (sem == SEM_FAILED) {
        perror("sem_open");
        return 1;
    }

    // 打开共享内存
    shm_fd = shm_open(SHM_NAME, O_RDONLY, 0666);
    shm_ptr = (pid_t *)mmap(NULL, SHM_SIZE, PROT_READ, MAP_SHARED, shm_fd, 0);

    // P操作:等待程序A写入完成
    sem_wait(sem);

    // 读取共享内存中的PID
    printf("程序B:从共享内存读取PID = %d\n", *shm_ptr);

    // 清理资源
    sem_close(sem);
    munmap(shm_ptr, SHM_SIZE);

    return 0;
}

注意事项

  • 匿名信号量仅适用于同一进程的线程间,具名信号量适用于不同进程间。
  • 信号量初始值:同步场景设为 0(等待通知),互斥场景设为 1(模拟互斥锁)。
  • 具名信号量需手动删除(sem_unlink),否则会残留文件。

条件量(Condition Variable)

定义

条件量用于线程间 “等待 - 通知” 机制,让线程在条件不满足时阻塞,条件满足时被唤醒。需与互斥锁配合使用,确保条件判断的原子性。

image

核心函数

初始化条件量

#include <pthread.h>// 动态初始化
/*
 * @brief 初始化条件量对象
 * @param cond:指向条件量对象的指针
 * @param attr:条件量属性,NULL表示默认属性
 * @return 成功返回0,失败返回非0错误码
 * @note 不可重复初始化已初始化的条件量
 */
int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

// 静态初始化
/*
 * @brief 静态初始化条件量,效果等同于动态初始化(attr=NULL)
 * @note 适用于全局或静态变量,无错误检查
 */
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

等待与唤醒

// 阻塞等待条件满足
/*
 * @brief 阻塞等待条件量通知,同时释放互斥锁
 * @param cond:已初始化的条件量对象指针
 * @param mutex:已上锁的互斥锁对象指针
 * @return 成功返回0,失败返回非0错误码
 * @note 原子操作:释放锁 + 阻塞等待,避免错过通知;
 *       唤醒后会自动重新获取锁;需配合while循环判断条件(避免虚假唤醒)
 */
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

// 限时等待条件满足
/*
 * @brief 限时等待条件量通知,超时后返回
 * @param cond:条件量对象指针
 * @param mutex:互斥锁对象指针
 * @param abstime:绝对超时时间(如当前时间+3秒)
 * @return 成功返回0,超时返回ETIMEDOUT,失败返回非0错误码
 */
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

// 唤醒一个等待的线程
/*
 * @brief 唤醒条件量上阻塞的一个线程(调度策略决定唤醒哪个)
 * @param cond:已初始化的条件量对象指针
 * @return 成功返回0,失败返回非0错误码
 * @note 若多个线程等待,仅唤醒一个;无等待线程时,该操作无效
 */
int pthread_cond_signal(pthread_cond_t *cond);

// 唤醒所有等待的线程
/*
 * @brief 唤醒条件量上阻塞的所有线程
 * @param cond:已初始化的条件量对象指针
 * @return 成功返回0,失败返回非0错误码
 * @note 适用于需要所有线程响应条件的场景
 */
int pthread_cond_broadcast(pthread_cond_t *cond);

销毁条件量

/*
 * @brief 销毁条件量,释放相关资源
 * @param cond:已初始化的条件量对象指针
 * @return 成功返回0,失败返回非0错误码
 * @note 只能销毁无线程等待的条件量;销毁后可重新初始化
 */
int pthread_cond_destroy(pthread_cond_t *cond);

代码示例:条件量与互斥锁配合实现同步

#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
// 临界资源:标志位
int flag = 0;
// 互斥锁(保护条件判断和临界资源)
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
// 条件量(实现等待-通知)
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

// 子线程A:模拟获取触摸屏坐标,满足条件则唤醒子线程B
void *task_A(void *arg) {
    while (1) {
        // 模拟获取触摸屏坐标(假设左上角坐标范围:x<100 && y<100)
        int x = rand() % 200;
        int y = rand() % 200;
        printf("线程A:触摸屏坐标 (%d, %d)\n", x, y);

        // 上锁:保护条件判断和flag
        pthread_mutex_lock(&mutex);
        if (x < 100 && y < 100) {
            flag = 1; // 条件满足,设置标志位
            printf("线程A:坐标在左上角,唤醒线程B\n");
            pthread_cond_signal(&cond); // 唤醒线程B
        }
        pthread_mutex_unlock(&mutex); // 解锁

        sleep(2);
    }
    return NULL;
}

// 子线程B:等待条件满足后输出字符串
void *task_B(void *arg) {
    while (1) {
        // 上锁:保护条件判断
        pthread_mutex_lock(&mutex);
        // 用while循环判断条件(避免虚假唤醒)
        while (flag <= 0) {
            pthread_cond_wait(&cond, &mutex); // 条件不满足,阻塞等待
        }
        /* 临界区:条件满足,执行操作 */
        printf("线程B:flag = %d,输出目标字符串!\n", flag);
        flag = 0; // 重置标志位
        /* 临界区结束 */
        pthread_mutex_unlock(&mutex); // 解锁
    }
    return NULL;
}

int main() {
    pthread_t tid_A, tid_B;

    // 创建子线程
    pthread_create(&tid_A, NULL, task_A, NULL);
    pthread_create(&tid_B, NULL, task_B, NULL);

    // 主线程等待子线程(实际不会退出)
    pthread_join(tid_A, NULL);
    pthread_join(tid_B, NULL);

    // 销毁资源(实际不会执行)
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond);

    return 0;
}

注意事项

  • 必须与互斥锁配合使用:条件判断和条件修改需在互斥锁保护下进行。
  • 避免虚假唤醒:用 while 循环判断条件,而非 if(系统可能无理由唤醒线程)。
  • 唤醒时机:修改条件后立即调用 pthread_cond_signal/broadcast,避免线程永久阻塞。
posted @ 2025-11-17 17:43  YouEmbedded  阅读(4)  评论(0)    收藏  举报