线程同步:条件变量实战指南

目录

1.条件变量的概念:

2.条件变量的使用

3.例子


锁有可能导致某一线程独占资源的情况。例如下面写的一个抢票程序,其中一个线程ID为820427008的线程在竞争锁时,频繁且快速的获得了锁,由于调度器的调度时机等因素,始终抢不过这个线程,导致这个线程能够持续获取锁,从而表现出独占资源的现象

cpp 复制代码
#include <pthread.h>
#include <cstdio>

#define NUM 5

pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
int ticket = 100;

void* get_ticket(void *args)//抢票
{
    pthread_detach(pthread_self());
    while(1)
    {
        pthread_mutex_lock(&lock);
        if(ticket > 0)ticket--;
        else
        {
            pthread_mutex_unlock(&lock);
            break;
        }
        printf("%d :snap up ticket\n",pthread_self());
        pthread_mutex_unlock(&lock);
    }
}

int main()
{
    for(int i = 0;i < NUM; i++)
    {
        pthread_t pid;
        pthread_create(&pid,nullptr,get_ticket,nullptr);
    }
    pthread_mutex_destroy(&lock);
    return 0;
}

1.条件变量的概念:

条件变量是线程可用的另一种同步机制。当与互斥量一起使用时,条件变量允许线程以无竞争的方式等待任意条件的发生。

**条件本身受互斥量保护的。**线程必须首先锁定互斥量才能改变条件的状态。其他线程在锁定互斥量之前不会注意到这种变化,因为必须锁定互斥量才能评估

2.条件变量的使用

  1. 创建一个pthread_cond_t的数据
  2. 进行初始化,可以常量PTHREAD_COND_INITIALIZER赋值,也可以使用pthread_cond_init函数对其进行初始化
  3. 使用pthread_cond_wait函数。传递给pthread_cond_wait函数的互斥量会保护条件。调用者将其锁定的互斥量传递给函数,然后该函数自动将此调用线程方式等待条件的线程列表中,并解锁互斥量。
  4. 陷入pthread_cond_wait阻塞的函数,等待被pthread_cond_signal或broadcast唤醒
  5. 程序运行结束后,调用pthread_cond_destroy销毁条件变量

用图解方式解释一下在pthread_cond_wait"睡眠"的线程

注意:如果是调用pthread_cond_broadcast唤醒所有线程 一定要用while来循环判断 不然会使得多个线程都同时访问独立资源了,锁就失去意义。

3.例子

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <pthread.h>
#include <unistd.h>

#define BUFFER_SIZE 5

// 缓冲区结构
typedef struct {
    int buffer[BUFFER_SIZE];
    int in;  // 生产者放入数据的位置
    int out; // 消费者取出数据的位置
    int count; // 缓冲区中数据的数量
} Buffer;

Buffer buffer;
// 互斥锁
pthread_mutex_t mutex;
// 条件变量:缓冲区不为空(供消费者等待)
pthread_cond_t cond_not_empty;
// 条件变量:缓冲区不为满(供生产者等待)
pthread_cond_t cond_not_full;

// 生产者线程函数
void* producer(void* arg) {
    int item, i = 0;
    while (1) {
        item = i++;
        // 获取互斥锁,保护临界区
        pthread_mutex_lock(&mutex);
        // 当缓冲区满时,生产者等待
        while (buffer.count == BUFFER_SIZE) {
            printf("缓冲区满,生产者等待...\n");
            // 等待条件变量cond_not_full,同时释放互斥锁,让其他线程(如消费者)可以操作
            pthread_cond_wait(&cond_not_full, &mutex);
        }
        // 生产数据,放入缓冲区
        buffer.buffer[buffer.in] = item;
        buffer.in = (buffer.in + 1) % BUFFER_SIZE;
        buffer.count++;
        printf("生产者生产了数据:%d,当前缓冲区数据量:%d\n", item, buffer.count);
        // 通知消费者,缓冲区不为空了
        pthread_cond_signal(&cond_not_empty);
        // 释放互斥锁
        pthread_mutex_unlock(&mutex);
        // 模拟生产耗时
        usleep(rand() % 1000000);
    }
    return NULL;
}

// 消费者线程函数
void* consumer(void* arg) {
    int item;
    while (1) {
        // 获取互斥锁,保护临界区
        pthread_mutex_lock(&mutex);
        // 当缓冲区空时,消费者等待
        while (buffer.count == 0) {
            printf("缓冲区空,消费者等待...\n");
            // 等待条件变量cond_not_empty,同时释放互斥锁,让其他线程(如生产者)可以操作
            pthread_cond_wait(&cond_not_empty, &mutex);
        }
        // 从缓冲区取出数据
        item = buffer.buffer[buffer.out];
        buffer.out = (buffer.out + 1) % BUFFER_SIZE;
        buffer.count--;
        printf("消费者消费了数据:%d,当前缓冲区数据量:%d\n", item, buffer.count);
        // 通知生产者,缓冲区不为满了
        pthread_cond_signal(&cond_not_full);
        // 释放互斥锁
        pthread_mutex_unlock(&mutex);
        // 模拟消费耗时
        usleep(rand() % 1000000);
    }
    return NULL;
}

int main() {
    pthread_t producer_thread, consumer_thread;
    // 初始化缓冲区
    buffer.in = 0;
    buffer.out = 0;
    buffer.count = 0;
    // 初始化互斥锁和条件变量
    pthread_mutex_init(&mutex, NULL);
    pthread_cond_init(&cond_not_empty, NULL);
    pthread_cond_init(&cond_not_full, NULL);
    // 创建生产者和消费者线程
    pthread_create(&producer_thread, NULL, producer, NULL);
    pthread_create(&consumer_thread, NULL, consumer, NULL);
    // 等待线程结束(实际中可能需要更合理的退出机制)
    pthread_join(producer_thread, NULL);
    pthread_join(consumer_thread, NULL);
    // 销毁互斥锁和条件变量
    pthread_mutex_destroy(&mutex);
    pthread_cond_destroy(&cond_not_empty);
    pthread_cond_destroy(&cond_not_full);
    return 0;
}
相关推荐
扶尔魔ocy17 小时前
【Linux C/C++开发】epoll模式的开源库及原生socket实现
linux·网络编程·epoll
hellojackjiang20115 天前
全面适配iOS 26液态玻璃,基于开源IM即时通讯框架MobileIMSDK:RainbowChat-iOS端v10.2发布
ios·网络编程·即时通讯·im开发·rainbowchat
deng-c-f5 天前
Linux C/C++ 学习日记(28):KCP协议(四):如何实现更复杂的业务:将连接状态的管理进行封装,用户只需实现发送、接收、断开的处理逻辑。
学习·网络编程·kcp
deng-c-f7 天前
Linux C/C++ 学习日记(22):Reactor模式(二):实现简易的webserver(响应http请求)
linux·c语言·网络编程·reactor·http_server
deng-c-f8 天前
Linux C/C++ 学习日记(27):KCP协议(三):源码分析与使用示例
linux·服务器·网络·c++·网络编程·kcp
deng-c-f8 天前
Linux C/C++ 学习日记(26):KCP协议(二):kcp源码分享
c语言·c++·学习·网络编程·kcp
Ronin30513 天前
【Linux网络】Socket编程:TCP网络编程
linux·网络·网络编程·tcp
沐浴露z14 天前
【深入理解计算机网络08】网络层之IPv4
网络·计算机网络·网络编程·信息与通信·408
沐浴露z18 天前
【深入理解计算机网络05】数据链路层:组帧,差错控制,流量控制与可靠传输
网络·计算机网络·网络编程·408
沐浴露z18 天前
【深入理解计算机网络04】通信基础核心知识全解析:从信号原理到物理层设备
计算机网络·网络编程·信息与通信·408