Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Linux线程同步与生产消费者模型

Linux线程同步与生产消费者模型

作者头像
有礼貌的灰绅士
发布于 2025-04-08 00:09:07
发布于 2025-04-08 00:09:07
10300
代码可运行
举报
运行总次数:0
代码可运行

多线程同步

同步的概念

举一个例子,假设有一个公厕,一堆人正在排队等着,在公厕里面的人正在使用,使用完毕之后刚出门,结果又进去了,在待一会,刚出来又进去了,那么后面的人怎么办呢? 在操作系统的线程当中也是的,如果多个线程同时在等待锁,那么刚刚释放完锁的那个线程竞争力非常强,如果这个线程释放锁之后没有别的任务,那么每次都会是固定的线程抢到锁。(线程饥饿问题) 这里如果释放锁没有除抢锁以外的时间消耗,如果想让这个线程去后面排队的方法,就是线程同步,也是让线程等待,刚刚释放完锁的线程会去后面等待,下一个线程会拿这个锁。

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。

条件变量解决死锁问题

拿到锁的线程如何去告诉下一个线程我使用完锁了,让下一个线程过来拿锁? 这里有一个叫条件变量的东西,他相当于一个铃铛,使用完毕就敲响这个铃铛,通知队列当中的线程。 条件变量函数 初始化:

int pthread_cond_init(pthread_cond_t *restrict cond,const pthread_condattr_t *restrict attr); 参数: cond:要初始化的条件变量 attr:NULL 条件变量的属性

销毁

int pthread_cond_destroy(pthread_cond_t *cond)

等待条件满足

int pthread_cond_wait(pthread_cond_t *restrict cond,pthread_mutex_t *restrict mutex); 参数: cond:要在这个条件变量上等待 mutex:互斥量

唤醒

int pthread_cond_broadcast(pthread_cond_t *cond); int pthread_cond_signal(pthread_cond_t *cond);

那么调教变量如何使用呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include<iostream>
#include<pthread.h>
#include<unistd.h>
using namespace std;
int count = 0;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;//初始化锁
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;//初始化条件变量

void* Count(void* args)
{
    pthread_detach(pthread_self());
    uint64_t number = (uint64_t)args;
    while(1)
    {
        pthread_mutex_lock(&mutex);
        pthread_cond_wait(&cond,&mutex);//让线程等待的时候会自动释放锁,如果不释放锁,lock外面的进不来
        cout << "pthread:" << number << "count:" << count++ << endl;
        pthread_mutex_unlock(&mutex);

    }
}
int main()
{
    for(int i = 0;i < 5;i++)
    {
        pthread_t tid;
        pthread_create(&tid,nullptr,Count,(void*)i);
    }
    while(1) 
    {
        sleep(1);
        pthread_cond_signal(&cond);//主线程唤醒等待队列中的其他线程,默认唤醒第一个
        cout << "signal one pthread" << endl; 
    }
    return 0;
}

那么我们如何知道要让一个线程去休眠呢?一定是临界资源未就绪,临界资源也是有状态的。 那么如果知道是否就绪?当然是去判断,判断也是访问临界资源,所以锁要加在判断条件的前面。

生产消费者模型(CP问题)

CP概念(consumer producter)

举个例子:我们的各种物品或食品的生产商家每次生产的东西都会放到超市里,顾客买东西也很少去厂家买,因为顾客需要买不同的商品,可能需要去各种厂家来回跑,并且厂家也不知道有多少人能买他的东西,只能来个人生产一份来一个人生产一份,很麻烦,所以就有了超市,超市的货架就是厂家生产商品放的地方,也是顾客购买商品的地方。(超市提高了所有人的效率)

生产者不用考虑消费者如何消费货品,消费者也不用考虑生产者需要生产什么货品,生产者与消费者的行为,进行了一定程度的解耦。 那么超市的货架就可以理解为一个超大号的缓存空间,是特定结构的内存。(共享资源,会有并发问题) 生产者与消费者都是线程,放到“货架”上的都是数据,也就是交换数据,进行一定程度的通信。

也就是说,如果会有并发问题,那么各个身份之间的关系就会有特殊的关系。(3种关系)

消费者与消费者:互斥(防止抢到一个东西) 生产者与生产者:互斥(防止“货架”满了,生产溢出) 消费者与生产者:互斥,同步(互斥是因为要确保数据的安全性,如果生产者生产的过程中,消费者过来拿数据,我们不确定数据有没有被拿走,有可能刚刚放上数据就被拿走了,内存不清楚,因为有不确定性,所以要生产和消费要保证原子性。同步是因为生产者会放满“货架”上的数据,消费者会拿光“货架”上的数据,只有一方满了或者另一方空对方才能知道要去生产or消费)

通过上述得知,满足以下条件才是生产消费者模型:

3种关系 2种角色(生产者与消费者) 1个交易场所(特定结构的内存空间)

代码实现CP模型

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//test.hpp
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<queue>
using namespace std;
template<class T>
class BlockQueue
{
    static const int defalutnum = 5;
public:
    BlockQueue(int maxcap = defalutnum):maxcap_(maxcap)
    {
        pthread_mutex_init(&mutex_,nullptr);
        pthread_cond_init(&c_,nullptr);
        pthread_cond_init(&p_,nullptr);

    }
    const T pop()//消费
    {
        pthread_mutex_lock(&mutex_);
        if(q_.size() == 0)
        {
            pthread_cond_wait(&c_,&mutex_);//想消费需要满足条件,如果没有资源,就不满足,需要进入队列等待资源补充
        }
        T out  = q_.front();
        q_.pop();
        pthread_cond_signal(&p_);//顺利消费一个说明队列不为满,通知生产者进行生产
        pthread_mutex_unlock(&mutex_);
        return out;
    }
    void push(const T &in)//生产
    {
        pthread_mutex_lock(&mutex_);
        if(q_.size() == maxcap_)//
        {
            pthread_cond_wait(&p_,&mutex_);//想生产需要满足条件,如果资源已满,就不满足,需要进入队列等待资源被拿走
        }
        q_.push(in);
        pthread_cond_signal(&c_);//顺利生产完一个说明队列里面有资源,通知消费者消费
        pthread_mutex_unlock(&mutex_);
    }
    ~BlockQueue()
    {
        pthread_mutex_destroy(&mutex_);
        pthread_cond_destroy(&c_);
        pthread_cond_destroy(&p_);
    }
private:
    queue<T> q_;//共享资源
    int maxcap_;//资源数量极值

    pthread_mutex_t mutex_;
    pthread_cond_t c_;
    pthread_cond_t p_;

};
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
//main.cc
#include "test.hpp"

void* Consunmer(void* args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int>*>(args);
    while(1)//消费
    {
        int data = bq->pop();
        cout << "消费了一个数据" << data << endl;
    }
}
void* Productor(void* args)
{
    BlockQueue<int> *bq = static_cast<BlockQueue<int>*>(args);
    int data = 0;
    while(1)//生产
    {
        sleep(1);
        data++;
        bq->push(data);
        cout << "生产了一个数据" << data << endl;
    }
}
int main()
{
    BlockQueue<int> *bq = new BlockQueue<int>();
    pthread_t c,p;
    pthread_create(&c,nullptr,Consunmer,bq);
    pthread_create(&p,nullptr,Productor,bq);

    pthread_join(c,nullptr);
    pthread_join(p,nullptr);
    delete bq;
    return 0;
}
细节1:CP模型是高效的

为什么说是高效的呢? 因为生产者要获取数据才能放进队列,消费者取到数据还有处理数据的过程,这个过程可以让生产者拿数据放数据,很少产生产生队列满or消费者等待的情况。

细节2:条件变量被误唤醒

比如你想唤醒一个线程,但是不小心把队列中的消息全都唤醒了,这就是误唤醒,也叫做伪唤醒。

这时等待队列全都被唤醒,有一个线程竞争成功了,往队列中添加一个数据,剩下竞争失败的线程会一直在外面等待,等到唤醒消费者的时候,消费者要和同时被误唤醒的生产者去竞争锁,万一生产者竞争成功了,那么就会往已满的队列当中继续添加数据:

那么如何避免这种情况呢?很简单,将if改成while()就可以了,多判断几次。

细节3:并发

CP模型是多个生产者与消费者在运行,很可能一个消费者正在拿,另一个消费者正在处理数据,并不会产生互斥,生产者也是同理,所以CP非常高效。 并且因为公用同一把锁,消费者和生产者用的也不是同一个等待队列,所以支持多个生产者消费者活动。(满足CP模型的关系)

POSIX信号量的另一个角度

在一个电影院中,很多人都在抢电影票,但是票数有限,也等于座位数,不可能让票数为0的时候还能被购买,信号量也是这样的,比如一个全局的计数器,加上锁之后,多个线程就可以安全的访问,虽然这个计数器是一份,但是可以被多个线程看成不同的一份,在某种意义上也是多份。

其实信号量的本质也就是计数器,那么这把计数器的本质是什么? 信号量有PV操作(P申请资源,V释放资源),也就是加加减减,并且这个操作是原子的,只要拿到了信号量就说明一定有这种资源,没拿到就一定没有。 所以信号量就是描述资源数目的,也就是资源是否就绪。 申请信号量也就间接的等于判断资源是否就绪了。

初始化信号量

#include <semaphore.h> int sem_init(sem_t *sem, int pshared, unsigned int value); 参数: pshared:0表示线程间共享,非零表示进程间共享 value:信号量初始值

销毁信号量

int sem_destroy(sem_t *sem);

等待信号量

功能:等待信号量,会将信号量的值减1 int sem_wait(sem_t *sem); //P()

发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1。 int sem_post(sem_t *sem);//V()

环形CP模型队列

这里空和满CP都是在同一个位置的。 我们可以加入信号量来讨论一下。

当P生产满了之后,P就会被挂起,这就是为什么生产者无法把消费者套一个圈。 然后让C进行消费,当资源数为0的时候,C挂起。 模拟实现一个单生产者单生产者的代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<queue>
#include<semaphore.h>
#include<ctime>
using namespace std;

const static int defaultcap = 5;
template<class T>
class RingQueue
{
private:
    void P(sem_t &sem)
    {
        sem_wait(&sem);
    }
    void V(sem_t &sem)
    {
        sem_post(&sem);
    }
public:
    RingQueue(int cap = defaultcap)
    :ringqueue_(cap),cap_(cap),c_step_(0),p_step_(0)
    {
        sem_init(&cdata_sem,0,0);
        sem_init(&pspace_sem,0,cap);

    }
    void push(const T& val)
    {
        P(pspace_sem);
        ringqueue_[p_step_] = val;
        p_step_++;
        p_step_ %= cap_;
        V(cdata_sem);


    }
    void pop(T *out)
    {
        P(cdata_sem);
        *out = ringqueue_[c_step_];
        c_step_++;
        c_step_ %= cap_;
        V(pspace_sem);


    }
    ~RingQueue()
    {
        sem_destroy(&cdata_sem);
        sem_destroy(&pspace_sem);
    }
private:
    vector<T> ringqueue_;
    int cap_;

    int c_step_;//消费者下标
    int p_step_;//生产者下标

    sem_t cdata_sem;//消费者关注的数据资源
    sem_t pspace_sem;//生产者关注的空间资源
};
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
#include "RingQueue.hpp"

void* Consunmer(void* args)
{
    RingQueue<int> *rq = static_cast<RingQueue<int>*>(args);
    int data = 0;
    while(1)//消费
    {
        
        rq->pop(&data);
        cout << "Consunmer..." << data << endl;
        sleep(1);
        
    }
}
void* Productor(void* args)
{
    RingQueue<int> *rq = static_cast<RingQueue<int>*>(args);
    int data = 0;
    sleep(3);//这里停顿3S,消费者3S中内也并没有运行
    while(1)//生产
    {
        data = rand() %10;
        rq->push(data);
        cout << "Productor..." << data << endl;
        sleep(1);
    }
}

int main()
{
    srand(time(nullptr)^getpid());
    RingQueue<int>* rq = new RingQueue<int>();
    pthread_t c,p;
    pthread_create(&c,nullptr,Consunmer,rq);
    pthread_create(&p,nullptr,Productor,rq);

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

那么如果想让支持多生产多消费该怎么办呢? 答案是加两把锁,生产者一把,消费者一把。 枷锁一定是在PV操作区间,这里注意,加锁最好实在P操作之前,因为信号量可以很多个线程申请成功,但是锁只能有一个线程竞争成功,假设A,B,C,D同时进入PV操作的区间,A的速度比较快,申请了信号量也申请了锁,然后B,C,D也来了,直接可以申请信号量,然后在申请锁的前面等待锁;如果是申请锁在申请信号量前面,只有申请锁才能够申请信号量,就导致只能一个一个去进入这个区间,效率会变慢。

池化技术

池化技术就是提前开辟好一块很大的空间,然后让线程在拿取空间的时候不需要在向操作系统申请,效率提高,操作系统开辟的空间全都放在线程池,线程一直去线程池拿空间,这其实也是CP模型。

线程安全的单例模式

单例模式是一种 “经典的, 常用的, 常考的” 设计模式. 什么是设计模式? IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式。 单例模式的特点 某些类, 只应该具有一个对象(实例), 就称之为单例。

饿汉模式 吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭。

伪代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template <typename T>
class Singleton {
static T data;
public:
static T* GetInstance() {
return &data;
}
};

只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例. 懒汉模式 吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式。 懒汉方式最核心的思想是 “延时加载”. 从而能够优化服务器的启动速度。 因为很多大型模块加载很慢,也导致服务器启动也很慢,所以用到的时候在加载,服务器就会启动的很快。

伪代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template <typename T>
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst == NULL) {
inst = new T();
}
return inst;
}
};

存在一个严重的问题, 线程不安全. 第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例. 但是后续再次调用, 就没有问题了.

那么如何解决不安全的问题呢?

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
template <typename T>
class Singleton {
volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.
static std::mutex lock;
public:
static T* GetInstance() {
if (inst == NULL) { // 双重判定空指针, 降低锁冲突的概率, 提高性能.
lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new.
if (inst == NULL) {
inst = new T();
}
lock.unlock();
}
return inst;
}
};

注意事项:

  1. 加锁解锁的位置
  2. 双重 if 判定, 避免不必要的锁竞争
  3. volatile关键字防止过度优化

STL,智能指针和线程安全

STL中的容器是否是线程安全的? 不是的:

原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响. 而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶). 因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.

智能指针是否是线程安全的?

对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题. 对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这 个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.

其他常见的各种锁

悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前, 会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。 CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。 自旋锁:和悲观锁对比,自旋锁会一直循环的查看这个锁有没有准备就绪,并不会去阻塞等待。 这是自旋锁的接口: 加锁:

第一个是非阻塞,第二个是阻塞。 解锁:

初始化与销毁:

读者与写者问题

在编写多线程的时候,有一种情况是十分常见的。那就是,有些公共数据修改的机会比较少。相比较改写,它们读的机会反而高的多。通常而言,在读的过程中,往往伴随着查找的操作,中间耗时很长。给这种代码段加锁,会极大地降低我们程序的效率。那么有没有一种方法,可以专门处理这种多读少写的情况呢? 有,那就是读写锁。

同时读者与写者也遵守CP模型的3种关系,2种角色,1个交易场所。

读者与读者:共享(因为所有人读一篇公告不会产生冲突) 写者与写者:互斥竞争 读者与写者:互斥,同步(如果写者没写完内容,读者去读,得到的信息就不完整)

读写锁的接口:

销毁与初始化,初始化第一个为读写锁,第二个为属性。

读者用第一个,写者用第二个。

无论读者还是写者,释放锁用这个。 伪代码:

如果读者数量为1,直接给写者加锁,不让写者进来写,如果写者此时已经再写, 说明锁没了,只能在lock(&wlock)这里阻塞了。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2025-03-23,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
【Linux】基于环形队列的生产消费者模型
在我们进行环形队列的生产消费者模型的学习之前,我们要对前置条件POSIX信号量进行学习,这里的POSIX的信号量与systemV的信号量是几乎一致的,都是用于同步操作,达到无冲突的访问共享资源的目的,只是POSIX信号量的使用要更简单一些,可以用于线程间同步
s-little-monster
2025/04/29
810
【Linux】基于环形队列的生产消费者模型
基于BlockingQueue的生产者消费者模型
生产者消费者模型一般可以在超市中听到,例如如下是一个专门卖方便面的超市,这个超市有自己供应商,也有客户来买,客户称之为消费者。超市起到一个缓存作用,供应商放假的时候,短时间内超市依然有对应的商品,消费者依然可以消费;相同的,如果短时间内消费者不来买东西,供应商依然可以供应给超市。也就是说,供应商生产产品比较慢,可以先生成一批产品放在超市中;供应商如果供应比较快,可以等消费者消费一段时间再去供应产品,协调忙线不均。现实生活中,在人口密集的地方肯定会有超市,生产者消费者模型效率高,有了超市这个巨大的缓存,可以使得消费者和生产者并发起来。
南桥
2024/08/24
1180
基于BlockingQueue的生产者消费者模型
【Linux探索学习】第三十二弹——生产消费模型:基于阻塞队列和基于环形队列的两种主要的实现方法
在前面我们已经学习了关于线程的主要知识,包括线程的基础知识以及线程的同步与互斥等内容,今天我们来学几个线程知识的拓展内容和几个经典的应用场景,比如:生产消费模型(cp问题)、线程池、有关线程的单例模式等,下面我们就来看一下这几点内容(本篇为生产消费模型,线程池和单例模式在后面讲)
GG Bond1
2025/03/06
740
【Linux探索学习】第三十二弹——生产消费模型:基于阻塞队列和基于环形队列的两种主要的实现方法
Linux:生产者消费者模型
       现实生活中,我们也会有像生物世界的生产者和消费者的概念,但是我们的消费者在大多数情况下并不和生产者直接联系,就比如说食物,不能说我今天去找供货商要十个面包,然后我还得在那等他把十个面包生产完了再走,虽然这对于生产者来说有多少需求就供应多少节约了成本,但是对于消费者来说却浪费了很多时间,我们作为消费者肯定希望我们去买的时候就能够买到,因此这个时候我们需要一个中间场所——超市,供应商可以一次性先生产一部分面包,然后把他摆到超市的货架上,这样消费者来拿的时候就可以直接拿了! 而当货架上快空了的时候,超市可以告知消费者先等一段时间再过来,然后通知生产者尽快生产,而货架满的时候,超市可以通知生产者先不要生产,而是让消费者尽量来消费。所以我们会发现这个中间场所让我们的交互变得更加理性化,我们生产者和消费者并不关心对方,而又超市这个中间场所来平衡两边的能力,这就是生产者消费者模型!!
小陈在拼命
2025/01/21
980
Linux:生产者消费者模型
Linux线程-生产消费模型和线程池
注:互斥关系保证的是数据的访问正常,而同步关系是为了让多线程(生产和消费者)之间协同起来
用户9645905
2022/11/30
3.3K0
Linux线程-生产消费模型和线程池
生产与消费者模型
  生产消费者模型的本质是讨论数据如何 并发的传递的问题,生活当中其实有很多生产消模型,经典模型比如超市。超市本身是不生产产品的,商品源来自于厂商,而客户源就是我们普通老百姓。而厂商可能不止一家,用户也不止一个,那为什么说超市是经典的模型呢?
用户11029129
2024/10/11
1560
生产与消费者模型
【Linux】从多线程同步到生产者消费者模型:多线程编程实践
如果某一个线程抢票能力过于强大,把所有的票一个人都抢走了,比如上面的线程4,一个人就抢到了8088张票,而线程2和线程3一张票都没有抢到,这就造成了线程2和线程3的饥饿问题!
用户11316056
2024/10/21
1110
【Linux】从多线程同步到生产者消费者模型:多线程编程实践
【Linux】基于阻塞队列的生产消费者模型
生产消费者模型就是通过一个容器来解决生产者和消费者的强耦合问题,生产者和消费者彼此之间不直接通讯,而是通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接交给阻塞队列,消费者不找生产者索要数据,而是直接从阻塞队列中去取,这样一来,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力
s-little-monster
2025/04/25
820
【Linux】基于阻塞队列的生产消费者模型
【Linux线程】Linux多线程实践:深入生产者消费者模型
🔍前言:在当今的软件开发领域,多线程编程已经成为了一种不可或缺的技术。特别是在Linux操作系统下,多线程编程的应用更是广泛而深入。而在多线程编程中,生产者消费者模型无疑是一个经典且重要的并发编程模式
Eternity._
2024/10/23
1720
【Linux线程】Linux多线程实践:深入生产者消费者模型
信号量——Linux并发之魂
今天,我们继续学习Linux线程本分,在Linux条件变量中,我们对条件变量的做了详细的说明,今天我们要利用条件变量来引出我们的另一个话题——信号量内容的学习。
破晓的历程
2024/08/20
1630
信号量——Linux并发之魂
初识Linux · 编写生产消费模型(1)
前文我们花了大量篇幅介绍了线程同步的概念,同时引出了条件变量,认识了相应的接口,并且快速编写了一个简单的测试用例见识了一下条件变量的使用,并且有意思的是,在Ubuntu环境下,man不了条件变量对应的接口,所以想要查询对应的接口可以使用对应的centos系统。
_lazy
2024/12/20
630
初识Linux · 编写生产消费模型(1)
【Linux】:多线程中的生产消费者模型
生产者-消费者模型(Producer-Consumer Model) 是多线程编程中的经典并发控制模型,主要用于协调多个线程之间的数据访问,防止竞争条件(race condition)和资源浪费,提高程序的并发能力。 抛开概念,我们用生活中的例子来举例——超市就是最好的例子。 超市充当着生产商和消费者的中间资源。 超市从生产商进货,生产商需要向超市提供货物,消费者在超市购物,超市需要向消费者提供商品。 如此一来,超市就成立生产者和消费者之间的桥梁。消费者就和生产者有一定的隔离,解决了生产者与消费者之间的强耦合。 得益于超市做缓冲区,整个生产消费的过程十分的高效,即便消费者没有在超市找到想要的商品,也可以借助超市向生产者进行反馈,从而做到生产对应的商品,也就是允许生产消费步调不一致。 生产消费者模型的本质就是:忙闲不均. 同时我们要知道超市不可能只面向单一的生产消费者,无论是哪一个,超市都会面向多个。也就是说,超市会被多个生产者消费者看到。 那么生产者、消费者间排列组合有什么关系呢? 生产者与生产者 还是以超市为例,多个生产者间存在互斥的关系,每个生产者都希望自己的产品能更多的出现到超市中,可以超市的空间始终是有限的,一个产品多了势必会影响到另一份的产品。比如可口与百事,统一与康师傅。 由此再一次得出结论:生产者与生产者存在互斥关系 消费者与消费者 假设超市准备打烊了,此时进来了两名顾客,他们看上了同一件商品,但是该商品以及卖的只省一件了,那么两名顾客必然就存在竞争的关系,也就是互斥。 由此得出:消费者与消费者间存在互斥关系. 生产者与消费者 依旧是同一个超市,某天顾客A进入超市打算购买商品A,但是此时的商品A已经卖完了,于是呢顾客A就去通知超市去备货,然后顾客A就离开了,走了一段路后,顾客A心想一定要买到商品A,于是又返回超市询问老板,商品A到了没有,可是还没有到。于是一整天的时间顾客A就在超市间往返,超市老板看他这么辛苦便要了顾客A的联系方式,声称一有货就通知他,于是顾客便回家安心等待了。 这个故事告诉我们什么呢? 超市老板添加顾客A的联系方式是为了将商品信息同步给生产者,这表明了生产者与消费者之间存在同步关系.同时,在超市的备货期间顾客是不能进行消费的,这表明了生产者与消费者之间存在互斥关系 由此得出:生产者与消费者间存在同步、互斥关系.
Yui_
2025/02/04
1580
【Linux】:多线程中的生产消费者模型
初识Linux · 编写生产消费模型(2)
前文我们介绍了基于阻塞队列实现生产消费模型,使用阻塞队列实现生产消费模型中,我们学习到了pthread_cond_wait的第二个参数的重要性,不仅会解锁,此时锁被其他人持有,当条件满足的时候,就重新竞争锁,所以在pthread_cond_wait函数这里是不会存在死锁的。
_lazy
2024/12/20
910
【Linux】基于阻塞队列和循环队列的生产者消费者模型
这里的生产者就是生产者线程,消费者就是消费者线程,这里的任务队列就是内存中某段内存区域。
用户11305458
2025/03/20
670
【Linux】基于阻塞队列和循环队列的生产者消费者模型
【linux学习指南】⽣产者消费者模型
⽣产者消费者模式就是通过⼀个容器来解决⽣产者和消费者的强耦合问题。⽣产者和消费者彼此之间不直接通讯,⽽通过阻塞队列来进⾏通讯,所以⽣产者⽣产完数据之后不⽤等待消费者处理,直接扔给阻塞队列,消费者不找⽣产者要数据,⽽是直接从阻塞队列⾥取,阻塞队列就相当于⼀个缓冲区,平衡了⽣产者和消费者的处理能⼒。这个阻塞队列就是⽤来给⽣产者和消费者解耦的。
学习起来吧
2025/02/17
450
【linux学习指南】⽣产者消费者模型
【Linux】生产消费模型实践 --- 基于信号量的环形队列
信号量本质是一个计数器,可以在初始化时对设置资源数量,进程 / 线程 可以获取信号量来对资源进行操作和结束操作可以释放信号量! 用于多进程 / 多线程 对共享数据对象的读取,它和管道有所不同,它不以传送数据为主要目的,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。 在资源只有一个时就一把互斥锁!
叫我龙翔
2024/08/22
1490
【Linux】生产消费模型实践 --- 基于信号量的环形队列
基于信号量和环形队列的生产者消费者模型
POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。 但POSIX可以用于线程间同步。信号量的本质是一个计数器。
南桥
2024/08/29
890
基于信号量和环形队列的生产者消费者模型
【在Linux世界中追寻伟大的One Piece】多线程(三)
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者索要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
枫叶丹
2024/12/02
830
【在Linux世界中追寻伟大的One Piece】多线程(三)
【Linux】互斥锁、基于阻塞队列、环形队列的生产消费模型、单例线程池
大部分情况,线程使用的数据都是局部变量,变量的地址空间在线程栈空间内,这种情况,变量归属单个线程,其他线程无法获得这种变量。 但有时候,很多变量都需要在线程间共享,这样的变量称为共享变量,可以通过数据的共享,完成线程之间的交互。而多个线程并发的操作共享变量,会带来一些问题。
_小羊_
2025/02/03
570
【Linux】互斥锁、基于阻塞队列、环形队列的生产消费模型、单例线程池
线程(三)生产者消费者模型+POSIX信号量
生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。这个阻塞队列就是用来给生产者和消费者解耦的。
海盗船长
2020/08/27
9110
推荐阅读
相关推荐
【Linux】基于环形队列的生产消费者模型
更多 >
LV.1
这个人很懒,什么都没有留下~
作者相关精选
领券
💥开发者 MCP广场重磅上线!
精选全网热门MCP server,让你的AI更好用 🚀
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验