【多线程】线程同步 {竞态条件与同步机制;条件变量,pthread_cond系列函数;基于BlockQueue的生产者消费者模型,条件变量的使用规范,生产者消费者模型如何提高效率?RAII加锁方式}

一、竞态条件与同步机制

竞态条件

  • 竞态条件(Race Condition)是指多个线程或进程同时访问共享资源,并且对资源的访问顺序不确定,导致最终结果的正确性依赖于线程执行的具体时序。竞态条件可能导致不可预测的结果,破坏程序的正确性和一致性。
  • 竞态条件通常发生在多个线程或进程同时对共享资源进行读写操作时,其中至少一个是写操作。当多个线程或进程同时读写共享资源时,由于执行顺序的不确定性,可能会导致数据的不一致性、丢失、覆盖等问题。
  • 为了避免竞态条件的发生,可以使用同步机制来限制对共享资源的访问,确保每次只有一个线程可以访问该资源。常见的同步机制包括互斥锁、信号量、条件变量等。此外,还可以使用原子操作和线程安全的数据结构来避免竞态条件的发生。

同步和互斥是并发编程中两个相关但不同的概念。

  • 互斥是为了解决安全问题,用于保护共享资源,确保在任意时刻只有一个线程可以访问该资源。互斥机制通过引入互斥锁(Mutex)来实现,当一个线程获得互斥锁时,其他线程必须等待,直到该线程释放锁才能继续访问共享资源。
  • 同步是在互斥保证了安全的前提下协调多个线程或进程的执行顺序,以确保它按照一定的顺序和规则高效地访问资源。同步机制可以通过互斥、信号量、条件变量等方式来实现,以确保线程或进程之间的有序执行。
  • 同步还可以包括其他机制,如条件变量、信号量等,用于实现更复杂的同步需求,例如线程间的通信和协作

二、条件变量的概念

只通过互斥保护共享资源不能够完全解决竞态条件问题,例如:

  1. 饥饿问题:在资源竞争的过程中,某执行流频繁的申请到资源,而导致其他执行流被长时间地阻塞或无法获得所需的资源时,就会发生饥饿问题。
  2. 忙等待问题:当访问临界资源时,除了要申请互斥锁,还要检测资源是否就绪。如上一章互斥锁中多线程售票的例子,还需要检测票数是否大于0,才能进行售票。当资源不就绪时,各线程只能通过频繁的轮询申请释放锁并进行检测才能确定资源是否就绪,浪费了CPU和锁资源。这就是忙等待问题。

因此,完全解决竞态条件问题不仅仅需要通过互斥保护共享资源,还必须通过其他同步机制进行线程间的通信和协作

条件变量

条件变量(Condition Variable)是一种线程同步机制,用于线程间的等待和唤醒操作,以实现线程间的协调和通信。条件变量通常与互斥锁(Mutex)结合使用,用于解决线程同步和竞态条件的问题。

条件变量提供了以下两个基本操作:

  1. 等待(Wait):一个线程调用等待操作后,会释放持有的互斥锁,并进入等待状态,直到其他线程通过唤醒操作将其唤醒。在被唤醒后,线程会重新申请互斥锁,并继续执行。

  2. 唤醒(Signal):一个线程调用唤醒操作后,会选择一个或多个等待在条件变量上的线程,并将其从等待状态唤醒。被唤醒的线程会尝试获取互斥锁,并继续执行。

条件变量的使用通常包括以下几个步骤:

  1. 创建并初始化条件变量:在程序中创建并初始化一个条件变量对象。
  2. 创建并初始化互斥锁:在程序中创建并初始化一个互斥锁对象,用于保护共享资源的访问。
  3. 等待条件:在需要等待某个条件满足的线程中,首先获取互斥锁,如果条件不满足,则调用条件变量的等待操作,将线程置于等待状态,同时会释放持有的互斥锁。
  4. 发送条件变量信号:在其他线程中,在满足某个条件时,可以发送条件变量信号,通知等待的线程继续执行。可以使用pthread_cond_signal函数发送信号,唤醒一个线程。有时候需要同时唤醒多个等待的线程,可以使用pthread_cond_broadcast函数进行广播。
  5. 接收条件变量信号:在等待线程中,收到条件变量信号后,线程被唤醒,再次尝试申请锁,如果申请锁成功则继续向后执行。

需要注意的是,条件变量需要与互斥锁配合使用,以保证对共享资源的访问是互斥的。同时,对条件变量的等待和唤醒操作需要在持有互斥锁的情况下进行,以确保操作的原子性和正确性。

条件变量配合互斥锁完美的解决了忙等待和饥饿问题。

  • 条件变量的等待是一种排队阻塞等待的操作,线程会按照先进先出的顺序等待条件变量的唤醒。这在一定程度上解决了多线程的饥饿问题。
  • 通过条件变量,线程可以在满足特定条件之前阻塞等待,而不是忙等待,从而提高了系统的效率和性能。同时在阻塞等待之前,它会释放所持有的互斥锁,允许其他线程获得该互斥锁并继续执行。这样,其他线程就有机会在等待期间获取互斥锁,并访问共享资源。这也在一定程度上解决了多线程的饥饿问题。

三、条件变量相关的函数

在pthread库中,提供了一些函数用于条件变量的操作。以下是一些常用的pthread库中条件变量相关的函数:

  1. pthread_cond_init:用于初始化条件变量。函数原型如下:

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

    参数cond是指向条件变量的指针,attr是条件变量的属性,通常设置为NULL表示使用默认属性。

    注意:和互斥锁变量的初始化相同:pthread_cond_init函数用于动态初始化,需用pthread_cond_destroy函数进行销毁。还可以在定义时使用PTHREAD_CONDITION_INTIALIZER进行静态初始化,静态初始化不需要手动销毁。

  2. pthread_cond_destroy:用于销毁条件变量。函数原型如下:

    int pthread_cond_destroy(pthread_cond_t *cond);
    

    参数cond是指向条件变量的指针。

  3. pthread_cond_wait:用于等待条件变量满足。函数原型如下:

    int pthread_cond_wait(pthread_cond_t *cond, pthread_mutex_t *mutex);
    

    参数cond是指向条件变量的指针,mutex是互斥锁。调用该函数时,线程会释放mutex互斥锁,并进入等待状态,直到其他线程通过pthread_cond_signal或pthread_cond_broadcast函数将其唤醒。

  4. pthread_cond_signal:用于唤醒等待在条件变量上的一个线程。函数原型如下:

    int pthread_cond_signal(pthread_cond_t *cond);
    

    参数cond是指向条件变量的指针。调用该函数时,会选择一个等待在条件变量上的线程,并将其唤醒。

  5. pthread_cond_broadcast:用于唤醒等待在条件变量上的所有线程。函数原型如下:

    int pthread_cond_broadcast(pthread_cond_t *cond);
    

    参数cond是指向条件变量的指针。调用该函数时,会将所有等待在条件变量上的线程都唤醒。

  6. pthread_cond_timedwait:用于等待条件变量满足,但可以设置最长等待时间。函数原型如下:

    int pthread_cond_timedwait(pthread_cond_t *cond, pthread_mutex_t *mutex, const struct timespec *abstime);
    

    参数cond是指向条件变量的指针,mutex是互斥锁,abstime是等待的最长时间。如果在指定的时间内条件变量未满足,线程会被唤醒。

这些函数提供了对条件变量的基本操作,可以实现线程间的等待和唤醒机制,用于解决线程同步和竞态条件的问题。在使用条件变量时,需要注意合理地配合互斥锁的使用,以保证对共享资源的访问是互斥的。

测试程序:主线程通过条件变量的等待和唤醒操作控制各子线程的执行顺序

#define THREAD_NUM 5
//退出标志
volatile bool quit = false; //volatile:禁止编译器将quit优化为寄存器变量

//线程工作任务函数的指针类型
typedef void (*func_t)(const std::string &name, pthread_mutex_t *pmtx, pthread_cond_t *pcond);

//func1线程工作任务函数
void func1(const std::string &name, pthread_mutex_t *pmtx, pthread_cond_t *pcond)
{
    while (!quit)
    {
        pthread_mutex_lock(pmtx);
        // 因为要检测临界资源是否就绪,所以要在加锁和解锁之间的临界区进行wait!
        // 各线程执行到wait函数时,排队阻塞等待条件变量
        pthread_cond_wait(pcond, pmtx); 
        std::cout << name << " call "
                  << "func1" << std::endl;
        pthread_mutex_unlock(pmtx);
    }
}

//func2,3,4,5工作任务函数与fun1相同,此处省略
//......

//工作任务函数表,用于循环派发任务
func_t funcs[THREAD_NUM] = {func1, func2, func3, func4, func5};

//线程入口点函数的参数内容
class ThreadData
{
public:
    std::string _name; //线程名
    func_t _func; //为该线程派发的工作任务函数
    pthread_mutex_t *_pmtx; //互斥锁的地址
    pthread_cond_t *_pcond; //条件变量的地址

    ThreadData(const std::string name, func_t func, pthread_mutex_t *pmtx, pthread_cond_t *pcond)
        : _name(name), 
          _func(func),
          _pmtx(pmtx),
          _pcond(pcond)
    {}
};

//各线程的入口点函数
void *Entry(void *arg)
{
    ThreadData *td = (ThreadData *)arg;
    td->_func(td->_name, td->_pmtx, td->_pcond);
    delete td;
    return nullptr;
}

int main()
{
    // 定义互斥锁和条件变量
    pthread_mutex_t mtx;
    pthread_cond_t cond;
    // 初始化互斥锁和条件变量(动态)
    pthread_mutex_init(&mtx, nullptr);
    pthread_cond_init(&cond, nullptr);
	// 循环创建多个子线程
    pthread_t tids[THREAD_NUM];
    for (int i = 0; i < THREAD_NUM; ++i)
    {
        std::string name = "child thread ";
        name += std::to_string(i + 1);
        ThreadData *td = new ThreadData(name, funcs[i], &mtx, &cond);
        pthread_create(tids + i, nullptr, Entry, (void *)td);
    }

    sleep(3); //在主线程休眠的3秒期间,所有的子线程都执行到pthread_cond_wait,排队阻塞等待条件变量

    // 控制各子线程的执行顺序
    int cnt = 10;
    while (cnt--)
    {
        // 当临界资源就绪时,唤醒等待条件变量的线程
        // 唤醒等待条件变量的一个线程
        std::cout << "condition signal! " << cnt << std::endl;
        pthread_cond_signal(&cond);
        // 唤醒等待条件变量的所有线程
        // std::cout << "condition broadcast!" << std::endl;
        // pthread_cond_broadcast(&cond);
        sleep(1);
    }
	// 设置退出标志,使各子线程退出
    quit = true;
    // 广播条件变量唤醒所有子线程,才能判断退出标志
    pthread_cond_broadcast(&cond); 
    // 循环等待多个子线程
    for (int i = 0; i < THREAD_NUM; ++i)
    {
        pthread_join(tids[i], nullptr);
        std::cout << "thread: " << tids[i] << " quit!" << std::endl;
    }
    // 销毁互斥锁和条件变量
    pthread_mutex_destroy(&mtx);
    pthread_cond_destroy(&cond);
    return 0;
}

运行结果:

在这里插入图片描述

  1. signal函数用于唤醒一个等待条件变量的线程,而broadcast函数用于唤醒所有等待条件变量。

  2. 当一个线程调用pthread_cond_wait时,它会首先释放所持有的互斥锁,并进入等待状态。此时,该线程会被放入条件变量的等待队列中,按照先进先出的顺序排队等待。因此线程的执行具有一定的顺序。

  3. 注意:我们无法确定到底哪一个线程先运行,因为线程的首次运行顺序(等待队列的顺序)完全由操作系统调度器决定。


四、生产者消费者模型

4.1 基本概念

在这里插入图片描述

  • 1个场所:数据缓冲区

  • 2种角色:生产者线程和消费者线程

  • 3种关系:

    1. 生产者和生产者:竞争和互斥关系,多个生产者不能同时进行生产操作。

    2. 消费者和消费者:竞争和互斥关系,多个消费者不能同时进行消费操作。

    3. 生产者和消费者:互斥和同步调关系,生产者和消费者不能同时进行生产和消费操作;生产者和消费者要能够互相通知,来确定资源是否就绪。

  • 生产者和消费者模型实现了线程的角色化,使不同角色的线程执行特定的工作。

  • 生产者能够确定容器中数据,而消费者能够确定容器中没有数据。以此来为条件变量发送信号唤醒线程提供依据

  • 生产者消费者模型中的容器实际上就是以一定形式组织的(数据结构)一段内存空间(缓冲区)

生产者消费者模型的优点

  1. 解耦:通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而是通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列;消费者不找生产者要数据,而是直接从阻塞队列里取。阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。

  2. 提高效率:缓冲区的存在,实现了生产者和消费者的并发执行。多生产多消费模型,实现了生产者和生产者,消费者和消费者的并发执行。(后面具体讲)


4.2 基于BlockingQueue的生产者消费者模型

在这里插入图片描述

在多线程编程中,阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构。其与普通的队列区别在于,当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中被放入了元素;当队列满时,往队列里存放元素的操作也会被阻塞,直到有元素被从队列中取出(以上的操作都是基于不同的线程来说的,线程在对阻塞队列进行操作时会被阻塞)。

提示:在进程间管道通信中,管道就是一个简单的阻塞队列,当管道中没有数据时,读端需要阻塞等待;当管道中的数据存满时,写端需要阻塞等待。

4.2.1 测试一:单线程生产,单线程消费

conprod.cc

#include "blockqueue.hpp"
#include <unistd.h>
using namespace std;

void *consumer(void *arg)
{
    BlockQueue<int> *bq = (BlockQueue<int> *)arg;
    while (true)
    {
        int buff = 0;
        bq->Pop(&buff);
        cout << "消费了一个数据:" << buff << endl; 
        sleep(1);
    }
    return nullptr;
}

void *producer(void *arg)
{
    BlockQueue<int> *bq = (BlockQueue<int> *)arg;
    int input = 0;
    while (true)
    {
        bq->Push(input);
        cout << "生产了一个数据:" << input << endl;
        ++input;
        //sleep(1);
    }
    return nullptr;
}

int main()
{
    BlockQueue<int> *bq = new BlockQueue<int>;
    pthread_t c, p;
    pthread_create(&c, nullptr, consumer, (void *)bq);
    pthread_create(&p, nullptr, producer, (void *)bq);

    pthread_join(c, nullptr);
    pthread_join(p, nullptr);
    return 0;
}

blockqueue.hpp

#pragma
#include <iostream>
#include <pthread.h>
#include <queue>
using namespace std;

const int DEF_CAP = 5;

template <class T>
class BlockQueue
{
    queue<T> _bq;
    int _capacity;
    pthread_mutex_t _mtx; //互斥锁
    pthread_cond_t _full; //条件变量,队列满时阻塞生产操作
    pthread_cond_t _empty; //条件变量,队列空时阻塞消费操作

    bool IsFull()
    {
        return _bq.size() == _capacity;
    }

    bool IsEmpty()
    {
        return _bq.size() == 0;
    }

public:
    BlockQueue(int capacity = DEF_CAP)
        : _capacity(capacity)
    {
        pthread_mutex_init(&_mtx, nullptr);
        pthread_cond_init(&_full, nullptr);
        pthread_cond_init(&_empty, nullptr);
    }
    void Push(const T &in)
    {
        //条件变量的等待和唤醒操作必须在持有互斥锁的条件下进行
        pthread_mutex_lock(&_mtx);
        //由于可能出现wait函数调用失败和伪唤醒的情况,所以通常需要将条件的检查与等待操作放在一个while循环中。
        while (IsFull()) 
            pthread_cond_wait(&_full, &_mtx); //释放锁_mtx,并排队阻塞等待条件变量_full
        //被唤醒的线程会尝试获取互斥锁,并继续执行。从哪里阻塞,就从哪里唤醒。
      	//访问临界资源
        _bq.push(in); 
        //唤醒消费者线程
        //if (_bq.size() >= _capacity / 2) // 当生产数量超过容量的一半时,再通知消费者消费
        pthread_cond_signal(&_empty); 
        pthread_mutex_unlock(&_mtx);
        // 在解锁前后都可以调用signal函数唤醒线程。可能出现各种各样的情况,
        // 如解锁后调用signal函数:解锁后,锁被其他非等待条件变量的线程获得,并进行生产和消费行为。此时再唤醒线程,线程又会去等待互斥锁。但我们其实并不关心到底是哪个线程进行消费和生产,我们只关心消费和生产的行为在资源就绪的条件下进行。
        // pthread_cond_signal(&_empty); 
    }
    void Pop(T *out)
    {
        pthread_mutex_lock(&_mtx);
        while (IsEmpty())
            pthread_cond_wait(&_empty, &_mtx);
        *out = _bq.front();
        _bq.pop();
        pthread_cond_signal(&_full);
        pthread_mutex_unlock(&_mtx);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mtx);
        pthread_cond_destroy(&_full);
        pthread_cond_destroy(&_empty);
    }
};

运行结果:生产快,消费慢(sleep 1)

在这里插入图片描述

伪唤醒
  • 条件变量的伪唤醒是指在没有满足特定条件的情况下,条件变量的等待操作被唤醒。这种情况可能是由于操作系统的调度策略或其他因素导致的,而不是由于满足了条件。
  • 伪唤醒可能会导致程序的错误行为或不正确的结果。当一个线程被伪唤醒时,它会重新获得互斥锁并从等待操作的地方继续执行,但实际上条件并没有满足。这可能会导致线程在不正确的状态下执行,或者导致竞争条件的发生。
  • 为了防止条件变量的伪唤醒,通常需要将条件的检查与等待操作放在一个while循环中。在等待操作被唤醒后,线程会再次检查条件是否满足,如果条件不满足,则继续等待。这样可以确保线程只在满足条件时才继续执行,避免了伪唤醒带来的问题。
条件变量的使用规范
void Pop(T *out)
{
    // 条件变量的等待和唤醒操作必须在持有互斥锁的条件下进行
    pthread_mutex_lock(&_mtx);
    // 由于可能出现wait函数调用失败和伪唤醒的情况,所以通常需要将条件的检查与等待操作放在一个while循环中。
    while (IsEmpty()) // 检查条件
        pthread_cond_wait(&_empty, &_mtx); // 等待条件
    // 访问临界资源
    *out = _bq.front();
    _bq.pop(); 
    pthread_cond_signal(&_full); // 唤醒条件
    pthread_mutex_unlock(&_mtx);
}
缓冲区如何提高效率?

在这里插入图片描述

  • 生产者获取数据(生产数据前)需要花时间,例如访问网络,读取磁盘数据;消费者处理数据(消费数据后)也需要花时间,例如进行数据计算,或者IO操作。
  • 生产数据和消费数据必须串行执行(互斥),但是获取数据和处理数据可以并行执行。当消费者处理数据时,生产者可以向缓冲区生产数据,也可以获取数据;当生产者获取数据时,消费者可以从缓冲区消费数据,也可以处理数据。
  • 因此缓冲区是通过实现生产者线程和消费者线程的并发执行来提高效率的。

4.2.2 测试二:多线程生产,多线程消费

conprod.cc

#include <iostream>
#include <pthread.h>
#include <unistd.h>
#include <ctime>
#include <cstdlib>
#include "blockqueue.hpp"
#include "task.hpp"

using namespace std;

int add(int x, int y)
{
    return x + y;
}

void *consumer(void *arg)
{
    BlockQueue<Task> *bq = (BlockQueue<Task> *)arg;
    while (true)
    {
        // 消费(获取)任务
        Task task;
        bq->Pop(&task);
        // 处理任务
        cout << "consumer[" << pthread_self() % 10000 << "]: " << task._x << " + " << task._y << " = " << task() << endl;
        // sleep(1);
    }
    return nullptr;
}

void *producer(void *arg)
{
    BlockQueue<Task> *bq = (BlockQueue<Task> *)arg;

    while (true)
    {
        // 制作任务
        int x = rand() % 10;
        usleep(rand() % 1000);
        int y = rand() % 10;
        Task task(x, y, add);
        // 生产任务
        bq->Push(task);
        cout << "producer[" << pthread_self() % 10000 << "]: " << task._x << " + " << task._y << " = ?" << endl;
        sleep(1);
    }
    return nullptr;
}

int main()
{
    srand((unsigned int)time(nullptr));
    BlockQueue<Task> *bq = new BlockQueue<Task>;
    //多生产者消费者
    pthread_t c[2], p[2];
    pthread_create(c, nullptr, consumer, (void *)bq);
    usleep(100);
    pthread_create(p, nullptr, producer, (void *)bq);
    usleep(100);
    pthread_create(c + 1, nullptr, consumer, (void *)bq);
    usleep(100);
    pthread_create(p + 1, nullptr, producer, (void *)bq);

    pthread_join(c[0], nullptr);
    pthread_join(p[0], nullptr);
    pthread_join(c[1], nullptr);
    pthread_join(p[1], nullptr);
    return 0;
}

task.hpp

#pragma once
#include <functional>
using namespace std;

typedef function<int(int, int)> func_t;

struct Task
{
    int _x;
    int _y;
    func_t _func;

    Task(int x = int(), int y = int(), func_t func = func_t())
        : _x(x),
          _y(y),
          _func(func)
    {
    }
    int operator()()
    {
        return _func(_x, _y);
    }
};

运行结果:

在这里插入图片描述

多生产多消费模型如何提高效率?

在这里插入图片描述

  1. 多个生产者线程在生产数据时必须串行执行(互斥),但在获取数据时(生产前)可以并发执行:当一个生产者正在获取数据时,其他生产者可以生产数据,也可以获取数据。
  2. 多个消费者线程在消费数据时必须串行执行(互斥),但在处理数据时(消费后)可以并发执行:当一个消费者正在处理数据时,其他消费者可以消费数据,也可以处理数据。
  3. 因此多生产多消费模型是通过实现生产者和生产者,消费者和消费者的并发执行来提高效率的。
  4. 注意:多生产多消费模型只适用于获取数据或处理数据比较花时间的场景,如IO操作,访问网络等。如果只是简单的处理过程就没有必要使用多生产多消费,因为线程切换反而成为负担。

五、RAII风格的加锁方式

RAII是指C++语言中的一个惯用法(idiom),它是“Resource Acquisition Is Initialization”的首字母缩写。中文可将其翻译为“资源获取就是初始化”。虽然从某种程度上说这个名称并没有体现出该惯性法的本质精神,但是作为标准C++资源管理的关键技术,RAII早已在C++社群中深入人心。

lockguard.hpp

#pragma once
//#include <pthread.h>

class Mutex
{
    pthread_mutex_t *_pmtx;

public:
    Mutex(pthread_mutex_t *pmtx)
        : _pmtx(pmtx)
    {
    }
    void lock()
    {
        pthread_mutex_lock(_pmtx);
    }
    void unlock()
    {
        pthread_mutex_unlock(_pmtx);
    }
};

class LockGuard
{
    Mutex _mutex;

public:
    LockGuard(pthread_mutex_t *pmtx)
        : _mutex(pmtx)
    {
        _mutex.lock(); //构造时加锁
    }
    ~LockGuard()
    {
        _mutex.unlock(); // 析构时解锁
    }
};

blockqueue.hpp

void Push(const T &in)
{
    // lockguard
    //构造时加锁
    LockGuard lockguard(&_mtx);  
    while (IsFull())
        pthread_cond_wait(&_full, &_mtx);
    _bq.push(in);
    pthread_cond_signal(&_empty); 
    //析构时解锁
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
pthread_cond是一种条件变量,它可以用于线程间的同步和通信。在多生产者和多消费者模型中,生产者线程负责生产数据,并将其放入共享队列中,而消费者线程则从队列中获取数据并进行处理。由于多个线程同时访问共享队列,因此需要使用pthread_cond来确保线程安全和同步。 以下是一些使用pthread_cond实现多生产者和多消费者模型的步骤: 1. 定义共享队列和锁 在多生产者和多消费者模型中,共享队列是所有线程都可以访问的数据结构。为了避免多个线程同时访问队列而导致数据不一致,需要使用锁来保护队列。可以使用pthread_mutex_t类型的锁来实现。 2. 定义条件变量 条件变量用于通知线程某个事件已经发生,例如队列已经有数据可以被消费或者队列已经满了无法再生产数据。在使用条件变量之前,需要先定义它。 3. 生产者线程 生产者线程的任务是生产数据并将其放入共享队列中。当队列已满时,生产者线程需要等待条件变量通知。 4. 消费者线程 消费者线程的任务是从共享队列中获取数据并进行处理。当队列为空时,消费者线程需要等待条件变量通知。 5. 发送信号 当生产者线程向队列中放入数据时,需要向条件变量发送信号,以通知消费者线程有数据可以被消费。同样,当消费者线程从队列中取出数据时,需要向条件变量发送信号,以通知生产者线程有空间可以生产数据。 6. 等待信号 当生产者线程或消费者线程需要等待条件变量通知时,可以使用pthread_cond_wait函数来等待。pthread_cond_wait函数会释放锁,并使线程进入睡眠状态,直到有其他线程发送信号。 7. 销毁条件变量和锁 在程序结束时,需要销毁条件变量和锁来释放内存资源。 综上所述,使用pthread_cond来实现多生产者和多消费者模型需要定义共享队列和锁,定义条件变量,实现生产者线程和消费者线程,发送信号和等待信号,以及销毁条件变量和锁。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

芥末虾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值