当前位置: 首页 > news >正文

【Linux学习】生产者-消费者模型

目录

        22.1 什么是生产者-消费者模型

        22.2 为什么要用生产者-消费者模型?

        22.3 生产者-消费者模型的特点

        22.4 BlockingQueue实现生产者-消费者模型

        22.4.1 实现阻塞队列BlockQueue

                1) 添加一个容器来存放数据

                2)加入判断Blocking Queue情况的成员函数

                3)实现push和pop方法

                4)完整代码

        22.4.2  [可选] 修改成RAII风格代码

        22.4.3 定义Blocking Queue中存放Task类任务

        22.4.4 生产者-消费者模型主函数实现

                1) 实现主函数

                2)定义任务函数

                3)定义消费者函数 consumer,生产者函数 producer

                4)完整代码

        22.4.5 makefile编译

        22.4.6 效果展示


        22.1 什么是生产者-消费者模型

生产者 - 消费者模型( Producer-consumer problem) 是一个非常经典的多线程并发协作的模型

​在这个模型中,有两种角色:

  • 生产者:生成数据并将其放入共享资源中
  • 消费者:从共享资源中获取数据并进行处理。

它们共享一个有限的资源,比如一个缓冲区。

我们可以用超市购物的场景来解释生产者-消费者模型:

  • 生产者:在这个例子中,生产者是超市的供应商。他们将各种商品(产品)送到超市的货架上,让消费者购买。供应商不断地提供新货物并放置在货架上。
  • 共享资源:在这个例子中,共享资源就是超市的货架。货架有限,无法容纳无限数量的商品。因此,货架可以看作是一个有界缓冲区,只能容纳一定数量的商品。
  • 消费者:消费者是超市的顾客。他们来到超市,从货架上选购商品,并将其购买。消费者会不断地从货架上取走商品。
  • 潜在问题:货架容量有限,供应商不能无限制地往货架上放商品,否则会导致货架满了,无法再放入新商品。同样,如果货架上没有商品了,顾客无法购买商品,会感到不满。超市需要协调供应商和顾客的行为。
  • 解决方案:供应商必须在货架有空间时才能往货架上放置商品,否则需要等待。顾客只有在货架上有商品时才能选购,否则需要等待。这种协调可以通过合适的管理和排队机制来实现,以确保货架的正常供应和顾客的购买需求。

        22.2 为什么要用生产者-消费者模型?

  • 缓冲和平衡负载

在多线程开发中,为了解决生产者和消费者之间速度不匹配的问题,常常会引入一个缓冲区来平衡生产和消费的速度差异。

缓冲区的作用是暂时存储生产者生产的数据,以便消费者在需要时取出。这样一来,即使生产者的速度比消费者快,生产者也不需要等待消费者立即处理数据,而是可以继续生产新的数据并将其放入缓冲区。同样,如果消费者的速度比生产者快,消费者也可以从缓冲区中取出数据并进行处理,而不必等待新数据的到来。

  • 解耦生产者和消费者

生产者和消费者可以独立运行,彼此之间无需直接交互。这种解耦可以简化系统的设计和维护,并且允许更容易地修改或替换生产者和消费者的实现。

        22.3 生产者-消费者模型的特点

多线程同步与互斥:生产者消费者模型是一个典型的多线程同步与互斥场景。多个生产者和消费者之间需要同步操作共享资源,同时确保互斥访问,避免数据竞争和不一致状态。

  • 三种关系

    • 生产者与生产者之间存在互斥关系:多个生产者不能同时往共享资源中添加数据,需要通过互斥机制保证只有一个生产者访问资源。
    • 消费者与消费者之间存在互斥关系:多个消费者不能同时从共享资源中取出数据,也需要通过互斥机制保证只有一个消费者访问资源。
    • 生产者与消费者之间存在互斥关系和同步关系:生产者生产数据后需要通知消费者进行消费,消费者消费完数据后需要通知生产者进行生产。这种同步关系确保生产者和消费者之间的顺序执行。
  • 两种角色:生产者和消费者是模型中的两种核心角色,通常由线程或进程来扮演。生产者负责生成数据并放入共享资源,而消费者负责从共享资源中取出数据并进行处理。

  • 一个交易场所:共享资源通常是一个缓冲区,用于暂时存储生产者生产的数据,以便消费者进行消费。这个交易场所可以是内存中的一段缓冲区,也可以是其他形式的数据结构,如队列、管道等。

我们用代码编写生产者消费者模型的时候,本质就是对这三个特点进行维护。

        22.4 BlockingQueue实现生产者-消费者模型

22.4.1 实现阻塞队列BlockQueue

阻塞队列(Blocking Queue)是一种常用于实现生产者和消费者模型的数据结构

阻塞队列为什么适用于实现生产者和消费者模型:

  • 当队列为空时,从队列获取元素的操作将会被阻塞,直到队列中放入了元素。
  • 当队列满时,往队列里存放元素的操作会被阻塞,直到有元素从队列中取出。

实现阻塞队列的基本原理:

  1. 阻塞队列通过使用互斥锁和条件变量来确保对队列的访问是线程安全的。互斥锁用于保护对队列的并发访问,而条件变量用于在适当的时候通知等待的线程。

  2. 当生产者要向队列中放入数据时,首先会获取互斥锁,以确保在放入数据的过程中不会被其他线程干扰。然后,生产者会检查队列是否已满,如果队列已满,则生产者会等待条件变量,直到队列有空闲空间为止。

  3. 同样地,当消费者要从队列中取出数据时,也会先获取互斥锁,以确保在取出数据的过程中不会被其他线程干扰。然后,消费者会检查队列是否为空,如果队列为空,则消费者会等待条件变量,直到队列中有数据可取。

  4. 这种同步和互斥机制确保了生产者和消费者之间的顺序执行。生产者和消费者之间通过条件变量进行通信,生产者负责向队列中放入数据,消费者负责从队列中取出数据,二者之间通过互斥锁确保对队列的安全访问。


介绍完原理,我们开始一步一步用代码来实现

1) 添加一个容器来存放数据

我们使用STL中现成的queue来模拟实现Blocking Queue ,这里我们创建一个名为BlockQueue.hpp的文件来定义BlockingQueue类

const int gDefaultCap = 5;
template <class T>
class BlockQueue
{
public:BlockQueue(int capacity = gDefaultCap) : capacity_(capacity){pthread_mutex_init(&mtx_, nullptr);pthread_cond_init(&Empty_, nullptr);pthread_cond_init(&Full_, nullptr);}~BlockQueue(){pthread_mutex_destroy(&mtx_);pthread_cond_destroy(&Empty_);pthread_cond_destroy(&Full_);}
private:std::queue<T> bq_;     // 阻塞队列int capacity_;         // 容量上限pthread_mutex_t mtx_;  // 通过互斥锁保证队列安全pthread_cond_t Empty_; // 用它来表示bq 是否空的条件pthread_cond_t Full_;  //  用它来表示bq 是否满的条件
};

这里我们默认capacity为5,具体可以通过修改gDefaultCap改变

2)加入判断Blocking Queue情况的成员函数
bool isQueueEmpty()
{return bq_.size() == 0;
}
bool isQueueFull()
{eturn bq_.size() == capacity_;
}

isQueueEmpty()判断队列是否为空:

当消费者试图从队列中取出数据时,如果队列为空,则消费者需要等待直到队列中有数据可取,以避免消费者线程空转浪费资源。

isQueueFull()判断队列是否已满:

当生产者试图向队列中放入数据时,如果队列已满,则生产者需要等待直到队列有空闲位置,以避免向已满的队列中添加数据。

3)实现push和pop方法
 void push(const T &in) // 生产者{pthread_mutex_lock(&mtx_);while(isQueueFull()) pthread_cond_wait(&Full_, &mtx_);bq_.push(in);if(bq_.size() >= capacity_/2) pthread_cond_signal(&Empty_);pthread_mutex_unlock(&mtx_);} void pop(T *out){pthread_mutex_lock(&mtx_);while (isQueueEmpty())pthread_cond_wait(&Empty_, &mtx_);*out = bq_.front();bq_.pop();pthread_cond_signal(&Full_);pthread_mutex_unlock(&mtx_);}

判断是否满足生产消费条件时不能用if,而应该用while:

  • pthread_cond_wait函数是让当前执行流进行等待的函数,是函数就意味着有可能调用失败,调用失败后该执行流就会继续往后执行。
  • 其次,在多消费者的情况下,当生产者生产了一个数据后如果使用pthread_cond_broadcast函数唤醒消费者,就会一次性唤醒多个消费者,但待消费的数据只有一个,此时其他消费者就被伪唤醒了。
  • 为了避免出现上述情况,我们就要让线程被唤醒后再次进行判断,确认是否真的满足生产消费条件,因此这里必须要用while进行判断。
4)完整代码
#pragma once
#include <iostream>
#include <queue>
#include <mutex>
#include <pthread.h>
const int gDefaultCap = 5;
template <class T>
class BlockQueue
{
private:bool isQueueEmpty(){return bq_.size() == 0;}bool isQueueFull(){return bq_.size() == capacity_;}public:BlockQueue(int capacity = gDefaultCap) : capacity_(capacity){pthread_mutex_init(&mtx_, nullptr);pthread_cond_init(&Empty_, nullptr);pthread_cond_init(&Full_, nullptr);}void push(const T &in) // 生产者{pthread_mutex_lock(&mtx_);while(isQueueFull()) pthread_cond_wait(&Full_, &mtx_);bq_.push(in);if(bq_.size() >= capacity_/2) pthread_cond_signal(&Empty_);pthread_mutex_unlock(&mtx_);} void pop(T *out){pthread_mutex_lock(&mtx_);while (isQueueEmpty())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(&Empty_);pthread_cond_destroy(&Full_);}
private:std::queue<T> bq_;     // 阻塞队列int capacity_;         // 容量上限pthread_mutex_t mtx_;  // 通过互斥锁保证队列安全pthread_cond_t Empty_; // 用它来表示bq 是否空的条件pthread_cond_t Full_;  //  用它来表示bq 是否满的条件
};

22.4.2  [可选] 修改成RAII风格代码

我们可以定义了一个 lockGuard 类,采用 RAII(资源获取即初始化)方式,对互斥锁进行加锁和解锁,确保在作用域结束时自动释放锁。

这里我们创建一个名为lockGuard.hpp:的文件来定义lockGuard类

#pragma once
#include <iostream>
#include <pthread.h>
class lockGuard
{
public:lockGuard(pthread_mutex_t *mtx) : mtx_(mtx){pthread_mutex_lock(mtx_);}~lockGuard(){pthread_mutex_unlock(mtx_);}private:pthread_mutex_t *mtx_; // 指向要管理的互斥锁的指针
};

lockGuard类的构造函数中,首先通过传入的pthread_mutex_t类型的指针初始化mtx_成员变量,即指向要管理的互斥锁。然后调用pthread_mutex_lock函数对该互斥锁进行加锁操作。

lockGuard类的析构函数中,调用pthread_mutex_unlock函数对互斥锁进行解锁操作。由于该析构函数在对象生命周期结束时自动调用,因此实现了互斥锁的自动释放。这样,在使用lockGuard对象时,只需要在作用域中创建该对象,当对象离开作用域时,析构函数会自动调用,从而释放互斥锁,确保了互斥锁的安全管理。

修改后的Blocking Queue代码

#pragma once
#include <iostream>
#include <queue>
#include <mutex>
#include <pthread.h>
#include "lockGuard.hpp"
const int gDefaultCap = 5;
template <class T>
class BlockQueue
{
private:bool isQueueEmpty(){return bq_.size() == 0;}bool isQueueFull(){return bq_.size() == capacity_;}
public:BlockQueue(int capacity = gDefaultCap) : capacity_(capacity){pthread_mutex_init(&mtx_, nullptr);pthread_cond_init(&Empty_, nullptr);pthread_cond_init(&Full_, nullptr);}void push(const T &in) // 生产者{lockGuard lockgrard(&mtx_); // 自动调用构造函数while (isQueueFull())pthread_cond_wait(&Full_, &mtx_);bq_.push(in);if(bq_.size() >= capacity_/2) pthread_cond_signal(&Empty_);} // 自动调用lockgrard 析构函数void pop(T *out){lockGuard lockguard(&mtx_);while (isQueueEmpty())pthread_cond_wait(&Empty_, &mtx_);*out = bq_.front();bq_.pop();pthread_cond_signal(&Full_);}~BlockQueue(){pthread_mutex_destroy(&mtx_);pthread_cond_destroy(&Empty_);pthread_cond_destroy(&Full_);}
private:std::queue<T> bq_;     // 阻塞队列int capacity_;         // 容量上限pthread_mutex_t mtx_;  // 通过互斥锁保证队列安全pthread_cond_t Empty_; // 用它来表示bq 是否空的条件pthread_cond_t Full_;  //  用它来表示bq 是否满的条件
};

22.4.3 定义Blocking Queue中存放Task类任务

现在我么已经实现了BlockQueue的逻辑,但是我们需要实现生产者生产资源后通过阻塞队列派发给消费者,这里我们不妨将派发的资源定义为一个Task类,生产者将Task任务派发给消费者完成

这里我们创建一个名为Task.hpp的文件来定义Task类

#pragma once
#include <iostream>
#include <functional>
typedef std::function<int(int, int)> func_t;
class Task
{
public:Task(){}Task(int x, int y, func_t func):x_(x), y_(y), func_(func){}int operator ()(){return func_(x_, y_);}
public:int x_;int y_;func_t func_;
};

重载了函数调用运算符 operator(),使得 Task 类的对象可以像函数一样被调用。在这个运算符重载函数中,调用了成员变量 func_ 所指向的函数对象,并传入 x_y_ 作为参数,返回函数调用的结果。

22.4.4 生产者-消费者模型主函数实现

这里我们创建一个名为pro-con.cc的文件来模拟实现生产者-消费者模型

1) 实现主函数
int main()
{srand((uint64_t)time(nullptr) ^ getpid() ^ 0x32457);BlockQueue<Task> *bqueue = new BlockQueue<Task>();pthread_t c[2],p[2];pthread_create(p, nullptr, productor, bqueue);pthread_create(p + 1, nullptr, productor, bqueue);sleep(1);pthread_create(c, nullptr, consumer, bqueue);pthread_create(c + 1, nullptr, consumer, bqueue);pthread_join(c[0], nullptr);pthread_join(c[1], nullptr);pthread_join(p[0], nullptr);pthread_join(p[1], nullptr);delete bqueue;return 0;
}
  • srand((uint64_t)time(nullptr) ^ getpid() ^ 0x32457);:用于初始化随机数生成器的种子。
  • BlockQueue<Task> *bqueue = new BlockQueue<Task>();:创建了一个 BlockQueue 类型的阻塞队列对象。
  • pthread_create:创建了两个消费者线程和两个生产者线程,并分别传入相应的函数指针和参数。
  • pthread_join:等待所有线程的完成。
  • delete bqueue;:释放了动态分配的阻塞队列对象的内存空间。
2)定义任务函数

我们设计的任务函数是两个参数的类型,为了方便演示,这里我们就简单写了一个加法Add函数来实现(有兴趣可以自己DIY!) 

int myAdd(int x, int y)
{return x + y;
}
3)定义消费者函数 consumer,生产者函数 producer
void* consumer(void *args)
{BlockQueue<Task> *bqueue = (BlockQueue<Task> *)args;while(true){Task t;bqueue->pop(&t);std::cout << pthread_self() <<" consumer: "<< t.x_ << "+" << t.y_ << "=" << t() << std::endl;}return nullptr;
}
void* productor(void *args)
{BlockQueue<Task> *bqueue = (BlockQueue<Task> *)args;while(true){int x = rand()%10 + 1;usleep(rand()%1000);int y = rand()%5 + 1;Task t(x, y, myAdd);bqueue->push(t);std::cout <<pthread_self() <<" productor: "<< t.x_ << "+" << t.y_ << "=?" << std::endl;sleep(1);}return nullptr;
}
  • void* consumer(void *args):消费者线程的入口函数。它接收一个 BlockQueue<Task> 类型的参数,并不断地从阻塞队列中取出任务对象,并执行任务函数。执行完毕后,打印出任务的计算结果。
  • void* productor(void *args):生产者线程的入口函数。它接收一个 BlockQueue<Task> 类型的参数,并不断地生成随机的任务对象,并将其推送到阻塞队列中。每个任务对象都包含两个随机生成的整数参数和任务函数的指针。生产者线程每次生成任务后,都会打印出任务的描述。
4)完整代码
#include "BlockQueue.hpp"
#include <pthread.h>
#include <unistd.h>
#include <ctime>
#include "Task.hpp"
int myAdd(int x, int y)
{return x + y;
}
void* consumer(void *args)
{BlockQueue<Task> *bqueue = (BlockQueue<Task> *)args;while(true){Task t;bqueue->pop(&t);std::cout << pthread_self() <<" consumer: "<< t.x_ << "+" << t.y_ << "=" << t() << std::endl;}return nullptr;
}
void* productor(void *args)
{BlockQueue<Task> *bqueue = (BlockQueue<Task> *)args;while(true){int x = rand()%10 + 1;usleep(rand()%1000);int y = rand()%5 + 1;Task t(x, y, myAdd);bqueue->push(t);std::cout <<pthread_self() <<" productor: "<< t.x_ << "+" << t.y_ << "=?" << std::endl;sleep(1);}return nullptr;
}int main()
{srand((uint64_t)time(nullptr) ^ getpid() ^ 0x32457);BlockQueue<Task> *bqueue = new BlockQueue<Task>();pthread_t c[2],p[2];pthread_create(p, nullptr, productor, bqueue);pthread_create(p + 1, nullptr, productor, bqueue);sleep(1);pthread_create(c, nullptr, consumer, bqueue);pthread_create(c + 1, nullptr, consumer, bqueue);pthread_join(c[0], nullptr);pthread_join(c[1], nullptr);pthread_join(p[0], nullptr);pthread_join(p[1], nullptr);delete bqueue;return 0;
}

22.4.5 makefile编译

pro-con:pro-con.ccg++ -o $@ $^ -std=c++11 -lpthread
.PHONY:clean
clean:rm -f pro-con

22.4.6 效果展示

相关文章:

【Linux学习】生产者-消费者模型

目录 22.1 什么是生产者-消费者模型 22.2 为什么要用生产者-消费者模型? 22.3 生产者-消费者模型的特点 22.4 BlockingQueue实现生产者-消费者模型 22.4.1 实现阻塞队列BlockQueue 1) 添加一个容器来存放数据 2)加入判断Blocking Queue情况的成员函数 3)实现push和pop方法 4)完…...

三、案例 - MySQL数据迁移至ClickHouse

MySQL数据迁移至ClickHouse 一、生成测试数据表和数据1.在MySQL创建数据表和数据2.在ClickHouse创建数据表 二、生成模板文件1.模板文件内容2.模板文件参数详解2.1 全局设置2.2 数据读取&#xff08;Reader&#xff09;2.3 数据写入&#xff08;Writer&#xff09;2.4 性能设置…...

[WinForm开源]概率计算器 - Genshin Impact(V1.0)

创作目的&#xff1a;为方便旅行者估算自己拥有的纠缠之缘能否达到自己的目的&#xff0c;作者使用C#开发了一款小型软件供旅行者参考使用。 创作说明&#xff1a;此软件所涉及到的一切概率与规则完全按照游戏《原神》(V4.4.0)内公示的概率与规则&#xff08;包括保底机制&…...

vscode 代码调试from IPython import embed

一、讲解 这种代码调试方法非常的好用。 from IPython import embed上面的代码片段是用于Python中嵌入一个交互式IPython shell的方法。这可以在任何Python脚本或程序中实现&#xff0c;允许在执行到该点时暂停程序&#xff0c;并提供一个交互式环境&#xff0c;以便于检查、…...

双活工作关于nacos注册中心的数据迁移

最近在做一个双活的项目&#xff0c;在纠结一个注册中心是在双活机房都准备一个&#xff0c;那主机房的数据如果传过去呢&#xff0c;查了一些资料&#xff0c;最终在官网查到了一个NacosSync 的组件&#xff0c;主要用来做数据传输的&#xff0c;并且支持在线替换注册中心的&a…...

5G NR 信道号计算

一、5G NR的频段 增加带宽是增加容量和传输速率最直接的方法&#xff0c;目前5G最大带宽将会达到400MHz&#xff0c;考虑到目前频率占用情况&#xff0c;5G将不得不使用高频进行通信。 3GPP协议定义了从Sub6G(FR1)到毫米波(FR2)的5G目标频谱。 其中FR1是5G的核心频段&#xff0…...

01-Spring实现重试和降级机制

主要用于在模块调用中&#xff0c;出现失败、异常情况下&#xff0c;仍需要进行重复调用。并且在最终调用失败时&#xff0c;可以采用降级措施&#xff0c;返回一般结果。 1、重试机制 我们采用spring 提供的retry 插件&#xff0c;其原理采用aop机制&#xff0c;所以需要额外…...

docker部署showdoc

目录 安装 1.拉取镜像 2.创建容器 使用 1.选择语言 2.默认账户/密码:showdoc/123456​编辑 3.登陆 4.首页 安装 1.拉取镜像 docker pull star7th/showdoc 2.创建容器 mkdir -p /opt/showdoc/html docker run -d --name showdoc --userroot --privilegedtrue -p 1005…...

2.14作业

1.请编程实现二维数组的杨辉三角。 2.请编程实现二维数组计算每一行的和以及列和。 3.请编程实现二维数组计算第二大值。 4.请使用非函数方法实现系统函数strcat,strcmp,strcpy,strlen. strcat: strcmp: strcpy: strlen:...

01.数据结构篇-链表

1.找出两个链表的交点 160. Intersection of Two Linked Lists (Easy) Leetcode / 力扣 例如以下示例中 A 和 B 两个链表相交于 c1&#xff1a; A: a1 → a2↘c1 → c2 → c3↗ B: b1 → b2 → b3 但是不会出现以下相交的情况&#xff0c;因为每个节点只有一个…...

揭秘产品迭代计划制定:从0到1打造完美迭代策略

产品迭代计划是产品团队确保他们能够交付满足客户需求的产品以及实现其业务目标的重要工具。开发一个成功的产品迭代计划需要仔细考虑产品的目标、客户需求、市场趋势和可用资源。以下是帮助您创建产品迭代计划的一些步骤&#xff1a;建立产品目标、收集客户反馈、分析市场趋势…...

Python进阶--下载想要的格言(基于格言网的Python爬虫程序)

注&#xff1a;由于上篇帖子&#xff08;Python进阶--爬取下载人生格言(基于格言网的Python3爬虫)-CSDN博客&#xff09;篇幅长度的限制&#xff0c;此篇帖子对上篇做一个拓展延伸。 目录 一、爬取格言网中想要内容的url 1、找到想要的内容 2、抓包分析&#xff0c;找到想…...

C语言--------数据在内存中的存储

1.整数在内存中的存储 整数在内存是以补码的形式存在的&#xff1b; 整型家族包括char,int ,long long,short类型&#xff1b; 因为char类型是以ASCII值形式存在&#xff0c;所以也是整形家族&#xff1b; 这四种都包括signed,unsigned两种&#xff0c;即有符号和无符号&am…...

【Java】零基础蓝桥杯算法学习——线性动态规划(一维dp)

线性dp——一维动态规划 1、考虑最后一步可以由哪些状态得到&#xff0c;推出转移方程 2、考虑当前状态与哪些参数有关系&#xff0c;定义几维数组来表示当前状态 3、计算时间复杂度&#xff0c;判断是否需要进行优化。 一维动态规划例题&#xff1a;最大上升子序列问题 Java参…...

Excel模板1:彩色甘特图

Excel模板1&#xff1a;彩色甘特图 分享地址 当前效果&#xff1a;只需要填写进度&#xff0c; 其余效果都是自动完成的 。 阿里网盘永久分享&#xff1a;https://www.alipan.com/s/cXhq1PNJfdm ​省心。能用公式的绝不使用手动输入。 ​​ 这个区域以及标题可以手动输入…...

如何重新安装 macOS

你可以使用电脑的内建恢复系统“macOS 恢复”来重新安装 Mac 操作系统。不但简单快捷&#xff0c;而且重新安装后不会移除你的个人数据。 将 Mac 关机 选取苹果菜单  >“关机”&#xff0c;然后等待 Mac 关机。如果你无法将 Mac 关机&#xff0c;请按住它的电源按钮最长 …...

论文阅读-Pegasus:通过网络内一致性目录容忍分布式存储中的偏斜工作负载

论文名称&#xff1a;Pegasus: Tolerating Skewed Workloads in Distributed Storage with In-Network Coherence Directories 摘要 高性能分布式存储系统面临着由于偏斜和动态工作负载引起的负载不平衡的挑战。本文介绍了Pegasus&#xff0c;这是一个利用新一代可编程交换机…...

【PTA|编程题|期末复习】字符串(一)

【C语言/期末复习】字符和字符串函数&#xff08;附思维导图/例题) 目录 7-1 组织星期信息 输入样例 (repeat3) : 输出样例: 代码 7-2 查找指定字符 输入格式&#xff1a; 输出格式&#xff1a; 输入样例1&#xff1a; 输出样例1&#xff1a; 输入样例2&#xff1a; …...

数据库基本操作2

一.DML&#xff08;Data Manipulation Language&#xff09; 用来对数据库中表的数据记录进行更新 关键字&#xff1a;增删改 插入insert 删除delete 更新update 1.数据插入 insert into 表&#xff08;列名1&#xff0c;列名2&#xff0c;列名3……&#xff09;values&a…...

BTC破5W+QAQ

比特币突破5万美元 创2021年来最高 比特币在龙年伊始涨超6.8%。在大年初四&#xff08;2月13日&#xff09;一度最高涨至5万零383美元。 今年1月&#xff0c;当市场期待已久的现货比特币交易所挂牌基金&#xff08;ETF&#xff09;推出后&#xff0c;比特币遭抛售&#xff0c…...

使用van-uploader 的UI组件,结合vue2如何实现图片上传组件的封装

以下是基于 vant-ui&#xff08;适配 Vue2 版本 &#xff09;实现截图中照片上传预览、删除功能&#xff0c;并封装成可复用组件的完整代码&#xff0c;包含样式和逻辑实现&#xff0c;可直接在 Vue2 项目中使用&#xff1a; 1. 封装的图片上传组件 ImageUploader.vue <te…...

基于Docker Compose部署Java微服务项目

一. 创建根项目 根项目&#xff08;父项目&#xff09;主要用于依赖管理 一些需要注意的点&#xff1a; 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件&#xff0c;否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...

GC1808高性能24位立体声音频ADC芯片解析

1. 芯片概述 GC1808是一款24位立体声音频模数转换器&#xff08;ADC&#xff09;&#xff0c;支持8kHz~96kHz采样率&#xff0c;集成Δ-Σ调制器、数字抗混叠滤波器和高通滤波器&#xff0c;适用于高保真音频采集场景。 2. 核心特性 高精度&#xff1a;24位分辨率&#xff0c…...

免费数学几何作图web平台

光锐软件免费数学工具&#xff0c;maths,数学制图&#xff0c;数学作图&#xff0c;几何作图&#xff0c;几何&#xff0c;AR开发,AR教育,增强现实,软件公司,XR,MR,VR,虚拟仿真,虚拟现实,混合现实,教育科技产品,职业模拟培训,高保真VR场景,结构互动课件,元宇宙http://xaglare.c…...

pikachu靶场通关笔记19 SQL注入02-字符型注入(GET)

目录 一、SQL注入 二、字符型SQL注入 三、字符型注入与数字型注入 四、源码分析 五、渗透实战 1、渗透准备 2、SQL注入探测 &#xff08;1&#xff09;输入单引号 &#xff08;2&#xff09;万能注入语句 3、获取回显列orderby 4、获取数据库名database 5、获取表名…...

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement

Cilium动手实验室: 精通之旅---13.Cilium LoadBalancer IPAM and L2 Service Announcement 1. LAB环境2. L2公告策略2.1 部署Death Star2.2 访问服务2.3 部署L2公告策略2.4 服务宣告 3. 可视化 ARP 流量3.1 部署新服务3.2 准备可视化3.3 再次请求 4. 自动IPAM4.1 IPAM Pool4.2 …...

comfyui 工作流中 图生视频 如何增加视频的长度到5秒

comfyUI 工作流怎么可以生成更长的视频。除了硬件显存要求之外还有别的方法吗&#xff1f; 在ComfyUI中实现图生视频并延长到5秒&#xff0c;需要结合多个扩展和技巧。以下是完整解决方案&#xff1a; 核心工作流配置&#xff08;24fps下5秒120帧&#xff09; #mermaid-svg-yP…...

解析两阶段提交与三阶段提交的核心差异及MySQL实现方案

引言 在分布式系统的事务处理中&#xff0c;如何保障跨节点数据操作的一致性始终是核心挑战。经典的两阶段提交协议&#xff08;2PC&#xff09;通过准备阶段与提交阶段的协调机制&#xff0c;以同步决策模式确保事务原子性。其改进版本三阶段提交协议&#xff08;3PC&#xf…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter

java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用&#xff08;Math::max&#xff09; 2 函数接口…...

C++ 类基础:封装、继承、多态与多线程模板实现

前言 C 是一门强大的面向对象编程语言&#xff0c;而类&#xff08;Class&#xff09;作为其核心特性之一&#xff0c;是理解和使用 C 的关键。本文将深入探讨 C 类的基本特性&#xff0c;包括封装、继承和多态&#xff0c;同时讨论类中的权限控制&#xff0c;并展示如何使用类…...