【Linux】:线程(三)同步和消费者模型

news/2024/11/15 3:53:03/

线程的同步

  • 一.条件变量
  • 二.生产者和消费者模型
    • 1.概念和特点
    • 2.实现基于阻塞队列的生产者消费者模型

同步:在保证数据安全的前提下,让线程能够按照某种特定的顺序访问临界资源,从而有效避免饥饿问题,叫做同步。
竞态条件:因为时序问题,而导致程序异常,我们称之为竞态条件。

一.条件变量

当一个线程互斥地访问某个变量时,它可能发现在其它线程改变状态之前,它什么也做不了。

例如一个线程访问队列时,发现队列为空,它只能等待,只到其它线程将一个节点添加到队列中。这种情况就需要用到条件变量。

创建条件变量

在这里插入图片描述

cond:要初始化的条件变量。
attr:NULL。

等待条件满足

在这里插入图片描述

唤醒等待

在这里插入图片描述

一个示例

我想让每一个线程依次对一个全局变量cnt做++操作。

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <sched.h>
#include <iostream>
using namespace std;
pthread_mutex_t mutex=PTHREAD_ADAPTIVE_MUTEX_INITIALIZER_NP;//初始化锁
pthread_cond_t cond=PTHREAD_COND_INITIALIZER;//初始化条件变量int cnt=0;void *route(void *arg)
{char *id =(char*)arg;while(1) {pthread_mutex_lock(&mutex);//上锁pthread_cond_wait(&cond,&mutex);//为什么在这等待?因为它在让线程等待时会自动释放锁cout<<id<<",cnt:"<<cnt++<<endl;pthread_mutex_unlock(&mutex);//解锁}
} 
int main()
{pthread_t t1, t2, t3, t4;pthread_create(&t1, NULL, route, (void*)"thread 1");pthread_create(&t2, NULL, route, (void*)"thread 2");pthread_create(&t3, NULL, route, (void*)"thread 3");pthread_create(&t4, NULL, route, (void*)"thread 4");sleep(1);cout<<"main thread begin:"<<endl;while(1){sleep(1);pthread_cond_signal(&cond);//唤醒正在等待的一个线程,默认是第一个//pthread_cond_broadcast(&cond);//唤醒所有线程cout<<"signal one pthread...."<<endl;}pthread_join(t1, NULL);pthread_join(t2, NULL);pthread_join(t3, NULL);pthread_join(t4, NULL);pthread_mutex_destroy(&mutex);//销毁return 0;
}

在这里插入图片描述

可以看到到此,所创建的线程均以一定顺序对cnt进行了++操作。注意这里的顺序不一定是1234,也有可能是2341…但它一定呈现周期性。

二.生产者和消费者模型

1.概念和特点

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

在这里插入图片描述

模型特点:

3种关系:消费者和消费者,生产者和生产者,生产者和消费者。

2种角色:消费者和生产者。

1个交易场所:特定的内存空间。

在这里插入图片描述

2.实现基于阻塞队列的生产者消费者模型

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

在这里插入图片描述

实现一个cp模型

设置水平线,当产品数量低于水平线时开始生产,当产品数量高于水平线时开始消费。

BlockQueue.hpp

#include <iostream>
#include <queue>
#include <pthread.h>
#include <unistd.h>template<class T>
class Blockqueue
{
public:Blockqueue(int capacity=20):capacity_(capacity)//默认容量是20{pthread_mutex_init(&mutex_, nullptr);//初始化锁pthread_cond_init(&c_cond_, nullptr);//初始化消费者条件变量pthread_cond_init(&p_cond_, nullptr);//初始化生产者条件变量water_=capacity_/3;//水平线}T pop(){pthread_mutex_lock(&mutex_);//上锁if(q_.size()==0){//如果没有产品,消费者开始等待pthread_cond_wait(&c_cond_,&mutex_);}T out=q_.front();q_.pop();if(q_.size()<water_){//如果数量小于水平线,唤醒生产者pthread_cond_signal(&p_cond_);}pthread_mutex_unlock(&mutex_);//解锁return out;}void push(const T&in){pthread_mutex_lock(&mutex_);//上锁if(q_.size()>capacity_){ //如果数量高于容量,生产者开始等待pthread_cond_wait(&p_cond_,&mutex_); }q_.push(in);if(q_.size()>water_){//如果数量高于水平线,唤醒消费者pthread_cond_signal(&c_cond_);}pthread_mutex_unlock(&mutex_);//解锁}~Blockqueue(){pthread_mutex_destroy(&mutex_);pthread_cond_destroy(&c_cond_);pthread_cond_destroy(&p_cond_);}private:std::queue<T> q_; // 共享资源int capacity_;     //容量pthread_mutex_t mutex_;pthread_cond_t c_cond_;pthread_cond_t p_cond_;int water_;
};

main.cc

#include"Blockqueue.hpp"void*Constumer(void*args)//消费者
{Blockqueue<int> *bq=static_cast<Blockqueue<int>*>(args);//强转while(1){int out=bq->pop();std::cout<<"消费了一个数据:"<<out<<std::endl;}
}void*Producer(void*args)//生产者
{Blockqueue<int> *bq=static_cast<Blockqueue<int>*>(args);int data=0;while(1){sleep(1);bq->push(data);std::cout<<"生产了一个数据:"<<data++<<std::endl;}
}int main()
{Blockqueue<int> *dp=new Blockqueue<int>();pthread_t t,c;pthread_create(&t,nullptr,Constumer,(void*)dp);pthread_create(&c,nullptr,Producer,(void*)dp);pthread_join(c,nullptr);pthread_join(t,nullptr);delete dp;return 0;
}

在这里插入图片描述


http://www.ppmy.cn/news/1268626.html

相关文章

JavaEE:单例模式(饿汉模式和懒汉模式)精讲

前言 什么是单例模式&#xff1f; 其实用通俗的话就是程序猿约定俗成的一些东西&#xff0c;就比如如果你继承了一个抽象类&#xff0c;你就要重写里面的抽象方法&#xff0c;如果你实现了一个接口&#xff0c;你就要重写里面的方法。如果不进行重写&#xff0c;那么编译器就会…

windows redis 允许远程访问配置

安装好windows版本的redis&#xff0c;会以服务方式启动&#xff0c;但是不能远程访问&#xff0c;这个时候需要修改配置。redis安装路径下会有2个配置文件&#xff0c;究竟需要怎么修改才能生效呢&#xff1f;看下图 这里的redis服务指定了是redis.windows-service.conf文件&…

RabbitMQ安装在Linux系统详细教程

安装教程&#xff1a; 1.首先将下载好的文件上传到服务器&#xff0c;拉到opt文件夹中(可以用xftp&#xff09; 2.输入命令&#xff1a; cd /opt 3.安装erlang rpm -ivh erlang-23.3.4.11-1.el7.x86_64.rpm rpm -ivh&#xff08;复制配置文件的名字&#xff09; 4.在Rab…

力扣二叉树--第四十一天

前言 写完这三道题&#xff0c;二叉树部分就先告一段落了。其实还有很多模糊的地方。 内容 一、修剪二叉搜索树 669. 修剪二叉搜索树 给你二叉搜索树的根节点 root &#xff0c;同时给定最小边界low 和最大边界 high。通过修剪二叉搜索树&#xff0c;使得所有节点的值在[l…

Hive命令操作

1.命令行模式 1. 获取帮助 --> hive -H 或-help 2. 运行hive语句 --> hive -e "执行语句" 3. 运行hive文件 --> hive –f "执行文件" 4. 定义变量 --> hive –hivevar keyvalue 5. 引用变量 --> ${varname} 2. 交互模式 1. 进入客户端 -…

RocketMQ如何保证消息的可靠性传递❓

RocketMQ 通过一系列的机制来保证消息的可靠性传递&#xff0c;确保在面对各种异常和故障情况时&#xff0c;消息系统能够稳定地处理和传递消息。以下是 RocketMQ 保证可靠性传递的关键机制&#xff1a; 1. 同步双写机制 (Synchronous Write Mechanism): RocketMQ的同步双写机…

ajax和Axios快速入门

什么是ajax 概念&#xff1a; Asynchronous JavaScript And XML&#xff0c;异步的JavaScrip和XML&#xff0c;重点在异步。 作用&#xff1a; 1&#xff0c;数据交互&#xff0c;可以通过ajax给服务器发送请求&#xff0c;并获取服务器响应的数据。 2&#xff0c;异步交互&am…

RocketMQ 的两种消息消费模式:Pull(拉取)和Push(推送)

RocketMQ 支持两种消息消费模式&#xff1a;Pull&#xff08;拉取&#xff09;和Push&#xff08;推送&#xff09;&#xff0c;它们之间有一些区别和联系。下面是它们的主要特点和比较&#xff1a; Pull&#xff08;拉取&#xff09;模式&#xff1a; 主动权在消费者&#x…