一、前言

        在 Linux 多线程编程中,条件变量是一种用于线程间同步的机制,核心作用是让线程在某个条件不满足时进入等待状态,直到其他线程触发该条件满足后再被唤醒,从而避免线程通过 “忙等” 浪费 CPU 资源。它通常与互斥锁配合使用,原因是条件的判断和修改必须是原子操作,防止出现竞态条件。

二、条件变量

2.1、条件变量原理

      条件变量的同步逻辑可以概括为 “等待 - 唤醒” 模型:

1、等待方线程:先加锁,检查条件是否满足。若不满足,则调用等待函数,自动释放互斥锁并进入阻塞状态;若满足,则执行后续逻辑。

2、唤醒方线程:先加锁,修改条件为满足状态,调用唤醒函数通知等待线程,最后释放互斥锁。

3、被唤醒的等待线程:会重新获取互斥锁,再次检查条件是否满足(防止虚假唤醒),满足则继续执行,不满足则再次等待。

2.2、关键特性

1、避免忙等:线程等待时会放弃 CPU 使用权,而非循环检查条件,大幅降低 CPU 消耗。

2、依赖互斥锁:条件的判断和修改必须在互斥锁的保护下进行,确保多线程下条件的一致性。

3、支持批量唤醒:可唤醒单个等待线程或所有等待线程。

2.3、相关函数

        Linux 下条件变量的相关接口定义在头文件 <pthread.h>中,核心数据结构是 pthread_cond_t。

2.3.1、条件变量初始化

1、静态初始化

        适用于全局或静态作用域的条件变量,使用宏PTHREAD_COND_INITIALIZER:

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
2、动态初始化

        适用于局部或堆上的条件变量,调用 pthread_cond_init 函数:

int pthread_cond_init(pthread_cond_t *restrict cond, const pthread_condattr_t *restrict attr);

        参数:cond:指向待初始化的条件变量。

                   attr:条件变量属性,通常设为 NULL 表示默认属性。

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

2.3.2、条件变量销毁

        使用 pthread_cond_destroy 函数释放资源,仅对动态初始化的条件变量有效

int pthread_cond_destroy(pthread_cond_t *cond);

        注:销毁前需确保没有线程正在等待该条件变量,否则会导致未定义行为。

2.3.3、等待函数

1、无限等待
int pthread_cond_wait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex);

        参数:cond:指向条件变量。

                   mutex:指向关联的互斥锁。

        核心行为:1、线程调用该函数时,必须已持有 mutex

                          2、函数会自动释放 mutex,并将线程加入条件变量的等待队列。

                          3、线程被唤醒后,会重新获取 mutex 锁,然后函数返回,此时线程需要重新检                                   查条件(防止虚假唤醒)。

2、限时等待
int pthread_cond_timedwait(pthread_cond_t *restrict cond, pthread_mutex_t *restrict mutex, const struct timespec *restrict abstime);

        参数:abstime是绝对时间(系统时间),而非相对时间。例如,设置等待 5 秒:

struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
ts.tv_sec += 5; // 5 秒后超时

        返回值:超时返回ETIMEDOUT,成功返回 0,失败返回其他错误码。

2.3.4、唤醒函数

1、唤醒单个线程
int pthread_cond_signal(pthread_cond_t *cond);

        作用:唤醒等待队列中的一个线程(具体唤醒哪个由系统调度决定)。

        注意:调用时建议持有互斥锁,也可以不持有,但持有锁能避免唤醒操作的时序问题。

2、唤醒所有线程
int pthread_cond_broadcast(pthread_cond_t *cond);

        作用:唤醒等待队列中的所有线程,适用于多个线程等待同一条件的场景(如生产者 - 消费者模型中的多个消费者)。

2.4、典型示例

        使用条件变量实现生产者,消费者模型,具体代码如下:

#include<stdio.h>
#include<pthread.h>
#include<stdlib.h>
#include<unistd.h>

typedef struct node
{
        int data;//存储节点的数据
        struct node* next;//指向同类型结构体的下一个节点
}Node;
//create head node
Node* head = NULL;//指向链表的第一个节点(栈顶),且链表为空
//create mutex
pthread_mutex_t mutex;
//create cond
pthread_cond_t cond;

void* produce(void* arg)
{
        while(1)
        {
                //create node
                Node* pnew = (Node*)malloc(sizeof(Node));//在堆上创建一个节点
                //init node
                pnew->data = rand()%1000;
                //lock
                pthread_mutex_lock(&mutex);
                //头插法入链表
                pnew->next = head;//新节点指向原来的头
                head = pnew;//更新头指针为新节点
                printf("produce: %ld,%d\n",pthread_self(),pnew->data);
                pthread_cond_signal(&cond);//通知“可能有货了”,唤醒一个正在 wait 的消费者
                //unlock
                pthread_mutex_unlock(&mutex);
                sleep(rand()%3);
        }
        return NULL;
}

void* customer(void* arg)
{
        while(1)
        {
                //lock
                pthread_mutex_lock(&mutex);
                if(head == NULL)//上锁后检查链表是否为空
                {
                        pthread_cond_wait(&cond,&mutex);
                }
                //delete head node
                Node* pdel = head;//保存要删除的头结点
                head = head->next;//头指针后移,相当于“出栈”
                printf("customer: %ld,%d\n",pthread_self(),pdel->data);
                free(pdel);//使用完后释放内存
                //unlock
                pthread_mutex_unlock(&mutex);
        }
        return NULL;
}
int main()
{
        //init mutex
        pthread_mutex_init(&mutex,NULL);
        //init cond
        pthread_cond_init(&cond,NULL);
        //create p1,p2
        pthread_t p1,p2;
        pthread_create(&p1,NULL,produce,NULL);
        pthread_create(&p2,NULL,customer,NULL);
        //p1,p2 join
        pthread_join(p1,NULL);
        pthread_join(p2,NULL);
        //kill mutex
        pthread_mutex_destroy(&mutex);
        //kill cond
        pthread_cond_destroy(&cond);
        return 0;
}

        编译并运行,结果如下:

        可以看到生产者生产出来的数据均被消费者给“吃掉了”。 

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐