Linux下的多线程编程:原理、工具及应用(3)

本文详细解释了POSIX线程库中的条件变量(如pthread_cond_t)的原理、初始化、销毁以及使用方法,包括与互斥锁的协作。同时介绍了生产者消费者模型中的BlockingQueue概念及其在多线程编程中的应用,提供了示例代码和进一步的封装实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

                                               🎬慕斯主页修仙—别有洞天

                                              ♈️今日夜电波:Flower of Life—陽花

                                                                0:34━━━━━━️💟──────── 4:46
                                                                    🔄   ◀️   ⏸   ▶️    ☰  

                                      💗关注👍点赞🙌收藏您的每一次鼓励都是对我莫大的支持😍


目录

条件变量再理解

pthread_cond_t

PTHREAD_COND_INITIALIZER

pthread_cond_init()

pthread_cond_destroy()

pthread_cond_wait()

pthread_cond_signal()

pthread_cond_broadcast()

示例代码

生产者消费者模型再理解

BlockingQueue 概念

BlockingQueue 的实现

示例代码

进一步封装


        从前面的理解中,我们对于死锁和条件变量有了一定程度的了解。对此,下面继续对于死锁和条件变量的共同作用加深理解。

条件变量再理解

pthread_cond_t

    pthread_cond_t是POSIX线程库中用于线程同步和通信的一种条件变量类型。它允许一个或多个线程等待直到其他线程通过信号通知特定条件已满足,从而实现线程间的协调工作。以下是关于pthread_cond_t的详细说明:

  1. 初始化:在开始使用pthread_cond_t之前,需要对其进行初始化。可以通过静态或动态的方式初始化条件变量。静态初始化通常在声明时直接赋予PTHREAD_COND_INITIALIZER值。如果选择动态初始化,则需要调用pthread_cond_init函数。
  2. 等待与唤醒机制:线程在等待某个条件变量时会进入睡眠状态,并释放其持有的互斥锁,这样其他线程可以执行相应的条件改变操作。当条件满足后,其他线程将通过pthread_cond_signalpthread_cond_broadcast函数唤醒等待该条件的线程。被唤醒的线程在返回前通常会再次获得互斥锁,以确保同步访问共享资源。
  3. 配合互斥锁使用:条件变量通常与互斥锁一起使用。线程在等待条件变量之前必须先锁定互斥锁,并在调用pthread_cond_wait之后解锁,以便其他线程可以访问共享资源并修改条件。在从pthread_cond_wait返回之前,线程会重新锁定互斥锁,以继续其工作。
  4. 销毁:当条件变量不再使用时,应调用pthread_cond_destroy函数进行清理,以避免资源泄露。
  5. 注意事项:在使用条件变量时要注意避免竞态条件和死锁,确保在检查条件和调用等待函数之间的操作是原子性的。
  6. 用途举例:条件变量常用于生产者-消费者问题、读写锁实现等多线程同步场景。

PTHREAD_COND_INITIALIZER

    PTHREAD_COND_INITIALIZER是POSIX线程库中用于初始化条件变量的宏。它的作用是将条件变量初始化为一个已定义的状态,以便在后续使用中进行比较和操作。

        具体来说,PTHREAD_COND_INITIALIZER是一个静态初始化器,可以在声明条件变量时直接将其赋值给条件变量。这个宏会将条件变量的内存设置为0,表示该条件变量尚未被初始化。

        以下是使用PTHREAD_COND_INITIALIZER进行条件变量初始化的示例代码:

#include <pthread.h>

pthread_cond_t cond = PTHREAD_COND_INITIALIZER;

        在这个例子中,我们声明了一个名为cond的条件变量,并使用PTHREAD_COND_INITIALIZER将其初始化为0。这样,我们就可以在后续的代码中使用cond来进行线程同步和通信的操作了。

        需要注意的是,PTHREAD_COND_INITIALIZER只能用于静态初始化,不能用于动态初始化。如果需要在运行时动态创建条件变量,需要使用pthread_cond_init()函数进行初始化。

pthread_cond_init()

    pthread_cond_init()是POSIX线程库中的一个函数,用于初始化条件变量。条件变量是一种同步机制,允许线程等待某个条件满足后再继续执行。

        该函数的原型如下:

#include <pthread.h>

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

参数说明:

  • cond:指向要初始化的条件变量的指针。
  • attr:指向条件变量属性对象的指针,可以设置为NULL表示使用默认属性。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • 在使用完条件变量后,需要调用pthread_cond_destroy()函数进行销毁,以释放相关资源。
  • 如果使用了自定义的属性对象,也需要在适当的时候调用pthread_condattr_destroy()函数进行销毁。

pthread_cond_destroy()

    pthread_cond_destroy()是POSIX线程库中的一个函数,用于销毁条件变量

        该函数的原型如下:

#include <pthread.h>

int pthread_cond_destroy(pthread_cond_t *cond);

参数说明:

  • cond:指向要销毁的条件变量的指针。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • 在使用完条件变量后,需要调用pthread_cond_destroy()函数进行销毁,以释放相关资源。
  • 如果条件变量正在被其他线程等待,则无法销毁该条件变量,直到所有等待该条件的线程已经返回。

pthread_cond_wait()

    pthread_cond_wait()是POSIX线程库中的一个函数,用于等待条件变量满足

        该函数的原型如下:

#include <pthread.h>

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

参数说明:

  • cond:指向要等待的条件变量的指针。
  • mutex:指向与条件变量关联的互斥锁的指针。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • pthread_cond_wait()函数会自动释放传入的互斥锁,并使当前线程进入阻塞状态,直到其他线程调用pthread_cond_signal()pthread_cond_broadcast()函数唤醒该线程。
  • 在调用pthread_cond_wait()函数之前,必须先加锁互斥锁,否则会导致未定义的行为。

pthread_cond_signal()

    pthread_cond_signal()是POSIX线程库中的一个函数,用于唤醒等待在条件变量上的一个线程。

        该函数的原型如下:

int pthread_cond_signal(pthread_cond_t *cond);

参数说明:

  • cond:指向要操作的条件变量的指针。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • pthread_cond_signal()函数只会唤醒等待在条件变量上的一个线程,如果有多个线程在等待,其他线程将继续等待。
  • 如果当前没有线程在等待条件变量,pthread_cond_signal()函数的行为是未定义的。
  • 在多线程编程中,通常需要结合互斥锁和条件变量来实现同步,确保线程安全。

pthread_cond_broadcast()

    pthread_cond_broadcast()是POSIX线程库中的一个函数,用于唤醒等待在条件变量上的所有线程。

        该函数的原型如下:

int pthread_cond_broadcast(pthread_cond_t *cond);

参数说明:

  • cond:指向要操作的条件变量的指针。

返回值:

  • 成功时返回0;失败时返回错误码。

注意事项:

  • pthread_cond_broadcast()函数会唤醒等待在条件变量上的所有线程,而不仅仅是一个线程。如果有多个线程在等待,它们都将被唤醒并继续执行。
  • 如果当前没有线程在等待条件变量,pthread_cond_broadcast()函数的行为是未定义的。
  • 在多线程编程中,通常需要结合互斥锁和条件变量来实现同步,确保线程安全。

示例代码

        使用互斥锁与条件等待来使得代码高效运行,以防某个线程一直占用锁从而占用资源!

#include <iostream> 
#include <unistd.h>
#include <pthread.h>
#include <string>

pthread_cond_t cond=PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex=PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;

int tickets=1000;

void *threadRoutine(void *args)
{
    std::string name=static_cast<const char*>(args);

    while(true)
    {
        pthread_mutex_lock(&mutex);
        if(tickets>0)
        {
            std::cout << name<< ", get a ticket: " << tickets-- << std::endl; // 模拟抢票
            usleep(1000);
        }else 
        {
            std::cout << "没有票了," << name << std::endl;
           // 1. 让线程在进行等待的时候,会自动释放锁 
           // 2. 线程被唤醒的时候,是在临界区内唤醒的,当线程被唤醒, 线程在pthread_cond_wait返回的时候,要重新申请并持有锁
           // 3. 当线程被唤醒的时候,重新申请并持有锁本质是也要参与锁的竞争的!!
            pthread_cond_wait(&cond,&mutex);
        }

        pthread_mutex_unlock(&mutex);

    }
}

int main()
{
    //child pthread
    pthread_t t1,t2,t3;
    pthread_create(&t1,nullptr,threadRoutine,(void*)"thread-1");
    pthread_create(&t2,nullptr,threadRoutine,(void*)"thread-2");
    pthread_create(&t3,nullptr,threadRoutine,(void*)"thread-3");

    //main pthread

    while(true)
    {

        sleep(5);
        pthread_mutex_lock(&mutex);
        tickets+=1000;
        pthread_mutex_unlock(&mutex);
        pthread_cond_broadcast(&cond);//给全部发信号
        //pthread_cond_signal(&cond);//给其中一个发信号
    }

        pthread_join(t1,nullptr);
        pthread_join(t1,nullptr);
        pthread_join(t1,nullptr);

        return 0;
    
}

生产者消费者模型再理解

BlockingQueue 概念

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

BlockingQueue 的实现

        主要是基于还是基于库中queue来进行包装,push作为生产者的生产操作,而pop作为消费者的消费操作其中的细节:当queue到达我们设定的满队列值时,需要根据条件变量来等待,而发生这个信号在消费者的pop函数。相对的pop函数中如果队列为空了,那么也需要等待push函数中的信号

BlockQueue.hpp

#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>

const int defaultcap = 5;//首先默认队列大小为5

template <class T>
class BlockQueue
{
public:
    BlockQueue(int cap = defaultcap)
        : _capacity(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_p_cond, nullptr);
        pthread_cond_init(&_c_cond, nullptr);
    }

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

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

    void Push(const T &in) // 生产者生产
    {
        pthread_mutex_lock(&_mutex);

    //本来使用的是if,但是为了防止pthread_cond_wait伪唤醒从而使用while
        while (IsFull()) // 写出来的代码,具有较强的鲁棒、健壮性
        {
            // 阻塞等待
            pthread_cond_wait(&_p_cond, &_mutex); // 1. 关于pthread_cond_wait在进一步理解!
        }
        _q.push(in);
        // if(_q.size() > _productor_water_line) pthread_cond_signal(&_c_cond);
        pthread_cond_signal(&_c_cond);
        pthread_mutex_unlock(&_mutex);
    }

    void Pop(T *out) // 消费者的
    {
    
        pthread_mutex_lock(&_mutex);
        while (IsEmpty())
        {
            // 阻塞等待
            pthread_cond_wait(&_c_cond, &_mutex);
        }

        *out = _q.front();
        _q.pop();
        //if(_q.size() < _consumer_water_line) pthread_cond_signal(&_p_cond);
        pthread_cond_signal(&_p_cond);
        pthread_mutex_unlock(&_mutex);
    }

    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_p_cond);
        pthread_cond_destroy(&_c_cond);
    }

private:
    std::queue<T> _q;
    int _capacity;
    pthread_mutex_t _mutex; // 锁
    pthread_cond_t _p_cond; // 生产者条件
    pthread_cond_t _c_cond; // 消费者的条件

};

示例代码

        需要特别注意其中的sleep,如果在消费者函数或者生产者函数中表示为另外一方先行执行!但是根据上面我们push和pop函数的相互等待条件。如果是消费者先执行,那么他会等待生产者生产,每生产一个就消费一个。而如果生产者先执行,则会在一瞬间生产很多,而后消费者每消费一个,生产者生产一个。

#include "BlockQueue.hpp"
#include <pthread.h>
#include <ctime>
#include <sys/types.h>
#include <unistd.h>

void *consumer(void *args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);

    while (true)
    {
        int data;

        //sleep(1);
        
        bq->Pop(&data);

        std::cout << "consumer data: " << data << std::endl;
    }

    return nullptr;
}

void *productor(void *args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int> *>(args);
    while (true)
    {
        int data = rand() % 10;
        std::cout << "productor task: " << data << std::endl;
        bq->Push(data);

        sleep(1);
    }
}

int main()
{
    srand((uint16_t)time(nullptr) ^ getpid() ^ pthread_self()); // 只是为了形成更随机的数据
    BlockQueue<int> *bq = new BlockQueue<int>();
    pthread_t c, p; // 消费者和生产者

    pthread_create(&c, nullptr, consumer, bq);
    pthread_create(&p, nullptr, productor, bq);

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

进一步封装

        进一步封装,延续第一篇的代码,把锁也一同封装了!让代码更加简洁!

LockGuard.hpp

#pragma once

#include <pthread.h>

// 不定义锁,默认认为外部会给我们传入锁对象
class Mutex
{
public:
    Mutex(pthread_mutex_t *lock):_lock(lock)
    {}
    void Lock()
    {
        pthread_mutex_lock(_lock);
    }
    void Unlock()
    {
        pthread_mutex_unlock(_lock);
    }
    ~Mutex()
    {}

private:
    pthread_mutex_t *_lock;
};

class LockGuard
{
public:
    LockGuard(pthread_mutex_t *lock): _mutex(lock)
    {
        _mutex.Lock();
    }
    ~LockGuard()
    {
        _mutex.Unlock();
    }
private:
    Mutex _mutex;
};

BlockQueue.hpp

#pragma  once

#include <iostream>
#include <queue>
#include <pthread.h>
#include "LockGuard.hpp"

const int defaultcap = 5; // for test

template<class T>
class BlockQueue
{
public:
    BlockQueue(int cap = defaultcap):_capacity(cap)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_p_cond, nullptr);
        pthread_cond_init(&_c_cond, nullptr);
    }
    bool IsFull()
    {
        return _q.size() == _capacity;
    }
    bool IsEmpty()
    {
        return _q.size() == 0;
    }
    void Push(const T &in) // 生产者的
    {
        LockGuard lockguard(&_mutex);
        // pthread_mutex_lock(&_mutex); // 2. lockguard 3. 重新理解生产消费模型(代码+理论) 4. 代码整体改成多生产,多消费
        while(IsFull()) // 写出来的代码,具有较强的鲁棒、健壮性
        {
            // 阻塞等待
            pthread_cond_wait(&_p_cond, &_mutex); // 1. 关于pthread_cond_wait在进一步理解!
        }

        _q.push(in);
        // if(_q.size() > _productor_water_line) pthread_cond_signal(&_c_cond);
        pthread_cond_signal(&_c_cond);
        // pthread_mutex_unlock(&_mutex);
    }
    void Pop(T *out)       // 消费者的
    {
        LockGuard lockguard(&_mutex);
        // pthread_mutex_lock(&_mutex);
        while(IsEmpty())
        {
            // 阻塞等待
            pthread_cond_wait(&_c_cond, &_mutex);
        }

        *out = _q.front();
        _q.pop();
        // if(_q.size() < _consumer_water_line) pthread_cond_signal(&_p_cond);
        pthread_cond_signal(&_p_cond);
        // pthread_mutex_unlock(&_mutex);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_p_cond);
        pthread_cond_destroy(&_c_cond);
    }
private:
    std::queue<T> _q;
    int _capacity; // _q.size() == _capacity, 满了,不能在生产,_q.size() == 0, 空,不能消费了
    pthread_mutex_t _mutex;
    pthread_cond_t _p_cond; // 给生产者的
    pthread_cond_t _c_cond; // 给消费者的


};


                      感谢你耐心的看到这里ღ( ´・ᴗ・` )比心,如有哪里有错误请踢一脚作者o(╥﹏╥)o! 

                                       

                                                                        给个三连再走嘛~  

评论 20
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

慕斯( ˘▽˘)っ

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

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

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

打赏作者

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

抵扣说明:

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

余额充值