【Linux系统】结合有趣的小故事让你学懂生产者消费者模型

news2024/11/24 16:03:48

目录

  • 由故事引入模型
    • 故事背景
    • 供货商们的矛盾
    • 市民们和供货商之间的矛盾一
    • 市民们和供货商之间的矛盾二
    • 市民们的矛盾
    • 模型总结
  • 生产者消费者模型
    • 为什么要使用生产者消费者模型?
    • 生产者消费者模型的特点
    • 生产者消费者模型优点
  • 基于BlockingQueue的生产者消费者模型
    • C++ queue模拟阻塞队列的生产消费模型
      • 小测试
      • 细节1 线程被误唤醒的情况
      • 细节2 生产者消费者模型高效在哪里?
      • 多生产者多消费者
      • 题外话

由故事引入模型

故事背景

有一个小朋友叫小C,他住的地方没有超市,只有几家供货商,因为每家供货商类型单一,买东西还要跑来跑去的,而且供货商晚上还不开门,买东西特别不方便,不仅小C觉得麻烦,其他人也觉得麻烦。小C想:为什么不能把这几家供货商的东西先放在一个地方呢,再由几个人专门卖,需要什么就直接挑选就好了,不用跑来跑去的,营业时间甚至可以全天。于是乎,小C就打电话给了市长,提了这个建议。市长知道了这个地方的市民买东西特别不方便,就接受了这个建议,于是就在这个地方建了个超市。
从此以后,小C和市民们买东西变得方便了,几家供货商把各种类型的商品送进超市,市民们只需要在超市进行挑选就可以了。大大节省了市民的时间,供应商也提高了工作效率,一次生产大批量的货物送进超市就好了,在货物充足时,供应商也能得到很好的休息,等货物缺乏再送过去。

由故事抽象出来的模型:
小C和市民们都是消费者,而供应商是生产者,超市是一种交易场所,为第三方
这就是生产者消费者模型,计算机中,生产者和消费者都是线程,第三方是一种特定数据结构的缓冲区。

线程之间想要通信,缓冲区一定要被所有线程看到, 也就是说缓冲区一定会被多线程并发访问, 那么缓冲区就要保护共享资源的安全,维护线程互斥与同步的关系。

供货商们的矛盾

由于超市的空间是有限的,这让供货商之间开始慢慢较量了,谁都想让自己的货物在超市多放一点。一天,供货商小S和供货商小D同时来超市放置自己的货物了。刚好超市这天只能放一家供货商的货物了,于是小S和小D就吵起来了。小S:“这块地方只有我能放货物,你不能放”。小D不服了:“凭什么只有你能放,我不能放?”于是两家供货商就大吵大闹,闹得沸沸扬扬的,不过这也不是一天两天的事了。超市知道了这件事后,就制定了一个叫做“锁”的规则:我这里有一把象征性的锁和钥匙,每天,谁能先拿到锁,谁就先放货物,放完后就解锁,下一次你们再继续竞争这把锁。

供货商是生产者,那么生产者和生产者之间的关系是竞争的关系。再极端一点,在线程中,我们叫互斥关系,同一时间一次只能执行一个线程。

市民们和供货商之间的矛盾一

小C早上想去超市买几箱可乐,很不巧超市没可乐了。于是小C过了一两小时又去超市问有可乐了吗,超市说没有。再过几个小时,小C再去,还是没可乐,过一会又去,还是没有。超市见小C频繁地来也不是个办法,就想了一个办法:你不要频繁的来了,你给我你的联系方式,等供货商送货来了,我再打电话给你。小C答应了这种请求。
超市想起前几个星期,超市货满放不下货物的时候,供货商也频繁地送货物来,每次都灰溜溜地回去了。于是超市也打电话对供货商说:你不要频繁地来了,你给我你的联系方式,等货物缺了,我再打电话给你,你再来。

小C想买可乐,但是超市没货,却隔一会就来问超市有货物吗。
供货商想送货进超市,但是超市货满了,却隔一会就问超市能进货了吗
这种可以抽象成线程的的频繁检测。
超市想出来的方案:等有货了再联系小C,等没货了再联系供应商。
可以抽象成缓冲区维护了生产者和消费者的同步关系,维护了线程之间的同步关系,让线程之间对第三方不再频繁的检测。

市民们和供货商之间的矛盾二

小C终于能去超市买可乐了,此时供货商小S想在这个地方放货物。由于超市空间限制,只能一个人在这里。小C:"让我先买东西,你再放。"供货商小S又不服气了:“上次是我的同行和我抢,这次怎么到你了?,让我先放”。两个人谁也不服谁。于是,“锁”规则又可以用起来了。

小C是消费者,供货商是生产者,生产者和消费者之间也有互斥关系。

市民们的矛盾

小C好不容易能买可乐了,可是小N来了,他也想买这几箱可乐。
于是小C又和小N吵起来了,之前制定的“锁”规则又起效果了。

小C和小N都是消费者,消费者和消费者之间是“互斥关系”

模型总结

  • 三种关系:生产者和生产者之间的关系(互斥),生产者和消费者之间的关系(互斥与同步),消费者和消费者之间关系(互斥)
  • 两种角色:生产者和消费者
  • 一个交易场所:通常是缓冲区

生产者消费者模型

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

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

在这里插入图片描述

生产者消费者模型的特点

由上面的故事已经进行总结。
生产者消费者模型是多线程同步与互斥的一个经典场景,其特点如下:

  • 三种关系: 生产者和生产者(互斥关系)、消费者和消费者(互斥关系)、生产者和消费者(互斥关系、同步关系)。
  • 两种角色: 生产者和消费者。(通常由进程或线程承担)
  • 一个交易场所: 通常指的是内存中的一段缓冲区。(可以自己通过某种方式组织起来)

在编写生产者消费者代码的时候,本质就上就是对三种特点进行维护。

生产者和生产者、消费者和消费者、生产者和消费者,它们之间为什么会存在互斥关系?

介于生产者和消费者之间的容器可能会被多个执行流同时访问,因此我们需要将该临界资源用互斥锁保护起来。

其中,所有的生产者和消费者都会竞争式的申请锁,因此生产者和生产者、消费者和消费者、生产者和消费者之间都存在互斥关系。

生产者和消费者之间为什么会存在同步关系?
  • 如果让生产者一直生产,那么当生产者生产的数据将容器塞满后,生产者再生产数据就会生产失败。
  • 反之,让消费者一直消费,那么当容器当中的数据被消费完后,消费者再进行消费就会消费失败。

虽然这样不会造成任何数据不一致的问题,但是这样会引起另一方的饥饿问题,是非常低效的。我们应该让生产者和消费者访问该容器时具有一定的顺序性,比如让生产者先生产,然后再让消费者进行消费。

注意: 互斥关系保证的是数据的正确性,而同步关系是为了让多线程之间协同起来。

生产者消费者模型优点

  • 解耦(生产者只负责生产,消费者只负责消费者)
  • 支持并发
  • 支持忙闲不均。· 假设没有缓冲区,且消费者和生产者的速度不匹配,则会造成CPU的浪费。生产者/消费者模型使得生产者/消费者的处理能力达到一个动态的平衡。

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

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

C++ queue模拟阻塞队列的生产消费模型

为了便于理解,这里以单生产者,单消费者为例。

先创建一个BlockingQueue类来充当我们的缓冲区。

#pragma once
#include <iostream>
#include <queue>

const int gcap = 5;//定义为 5方便后面进行测试
template <class T>
class BlockingQueue
{
public:
    BlockingQueue(const int cap = gcap) : _capapacity = cap
    {
    }
    !BlockingQueue()
    {
    }

private:
    std::queue<T> _q;//队列
    int _capacity;//队列的容量上限
};

生产者消费者模型是用在多线程场景下的,所以要我们要保证它是线程安全的,要保证线程互斥和线程同步。所以要加上锁和条件变量

  • 在这个模型中,由于我们要避免生产者和消费者同时访问一份资源,只需要一把锁就够了。
  • 但是条件变量需要两个。我们的要求是:当队列为空时,从队列中获取元素会被阻塞,直到队列中放入了元素;当队列为满时,往队列里存放元素也会被阻塞,直到队列里有元素被取出。所以一个条件变量是不够的,需要两个条件变量,分别表示满和空。
#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>

const int gcap = 5;//定义为5方便后面进行测试
template <class T>
class BlockingQueue
{
public:
    BlockingQueue(const int cap = gcap) : _capacity(cap)
    {
        pthread_mutex_init(&_mutex,nullptr);//初始化锁
        pthread_code_init(&_full,nullptr);//初始化条件变量
        pthread_code_init(&_empty,nullptr);//初始化条件变量
    }
    ~BlockingQueue()
    {
        pthread_mutex_destroy(&mutex);
        pthread_cond_destroy(&_full);
        pthread_cond_destroy(&_empty);
    }

private:
    std::queue<T> _q;//队列
    int _capacity;//队列的容量上限
    pthread_mutex_t _mutex;//定义锁
    pthread_cond_t _full;//条件变量,满时生产者阻塞
    pthread_cond_t _empty;//空时消费者阻塞
};

判空和判满函数

    bool isFull(){return _q.size() == _capacity;}
    bool isEmpty(){return _q.empty();}

首先先粗略写一下生产者要完成的任务:往容器里面放元素。这个时候需要判断容器是否是满的。

    void push(const T& in)//生产者把元素放进容器
    {
        pthread_mutex_lock(&_mutex);//加锁保证线程安全
        if(isFull())//判断容器是否为满
        {
            //如果满了就进行等待
            pthread_cond_wait(&_full,&_mutex);
        }
        _q.push(in);//未满,就生产,放进容器
        pthread_mutex_unlock();
    }

这里简单谈一下pthread_cond_wait这个函数

  • 我们只能在临界区内部,判断临界资源是否就绪,这就注定了在我们在当前一定是持有锁的。
  • 要让线程进行休眠等待,就不能持有锁等待。
  • 这就说明,pthread_cond_wait 要有锁的释放能力。
  • 当线程醒来的时候,会继续从临界区内部继续运行,因为是在临界区被切走的。
  • 注定了当线程被唤醒的时候,继续在pthread_cond_wait 函数向后运行,又要重新申请锁,申请成功才会返回

接下来再粗略写一下消费者要做的事情:从容器取元素。如果容器为空就等待。

    void pop(T* out)
    {
        pthread_mutex_lock(&_mutex);//加锁保证线程安全
        if(isEmpty())//判断容器是否为空
        {
            pthread_cond_wait(&_empty,&_mutex);//如果为空,消费者就进行等待
        }
        *out = _q.front();//不为空,就取队列头部元素
        _q.pop();//取出以后,队列弹出该元素
        pthread_mutex_unlock(&_mutex);
    }

这两段生产者和消费者各自执行各自任务的代码是有问题的。

  • 假如生产者要往容器存数据的时候,判断容器是满的,那么就去等待了。
  • 此时消费者继续消费,当消费到容器为空时,消费者又去等待了。
    此时问题就是,没人能唤醒生产者和消费者。
    解决方法如下:

互相唤醒对方

  • 当生产者能生产时,每次都使用函数唤醒消费者。
  • 当消费者能消费时,每次都使用函数唤醒生产者。

代码如下:

    void push(const T& in)//生产者把元素放进容器
    {
        pthread_mutex_lock(&_mutex);//加锁保证线程安全
        if(isFull())//判断容器是否为满
        {
            //如果满了就进行等待
            pthread_cond_wait(&_full,&_mutex);
        }
        _q.push(in);//未满,就生产,放进容器
        //此时可以加一些策略,比如容量为多少时就唤醒,我们这里就不加了。
        pthread_cond_signal(&_empty);
        pthread_mutex_unlock(&_mutex);
    }
    void pop(const T* out)
    {
        pthread_mutex_lock(&_mutex);//加锁保证线程安全
        if(isEmpty())//判断容器是否为空
        {
            pthread_cond_wait(&_empty,&_mutex);//如果为空,消费者就进行等待
        }
        *out = _q.front();//不为空,就取队列头部元素
        _q.pop();//取出以后,队列弹出该元素
        pthread_cond_signal(&_full);
        pthread_mutex_unlock(&_mutex);
    }

目前代码如下:

#pragma once
#include <iostream>
#include <queue>
#include <pthread.h>
using namespace std;
const int gcap = 5;//定义为5方便后面进行测试
template <class T>
class BlockingQueue
{
public:
    BlockingQueue(const int cap = gcap) : _capacity(cap)
    {
        pthread_mutex_init(&_mutex,nullptr);//初始化锁
        pthread_cond_init(&_full,nullptr);//初始化条件变量
        pthread_cond_init(&_empty,nullptr);//初始化条件变量
    }
    bool isFull(){return _q.size() == _capacity;}
    bool isEmpty(){return _q.empty();}
    void push(const T& in)//生产者把元素放进容器
    {
        pthread_mutex_lock(&_mutex);//加锁保证线程安全
        if(isFull())//判断容器是否为满
        {
            //如果满了就进行等待
            pthread_cond_wait(&_full,&_mutex);
        }
        _q.push(in);//未满,就生产,放进容器
        //此时可以加一些策略,比如容量为多少时就唤醒,我们这里就不加了。
        pthread_cond_signal(&_empty);
        pthread_mutex_unlock(&_mutex);
    }
    void pop(T* out)
    {
        pthread_mutex_lock(&_mutex);//加锁保证线程安全
        if(isEmpty())//判断容器是否为空
        {
            pthread_cond_wait(&_empty,&_mutex);//如果为空,消费者就进行等待
        }
        *out = _q.front();//不为空,就取队列头部元素
        _q.pop();//取出以后,队列弹出该元素
        pthread_cond_signal(&_full);
        pthread_mutex_unlock(&_mutex);
    }
    ~BlockingQueue()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_full);
        pthread_cond_destroy(&_empty);
    }

private:
    std::queue<T> _q;//队列
    int _capacity;//队列的容量上限
    pthread_mutex_t _mutex;//定义锁
    pthread_cond_t _full;//条件变量,满时生产者阻塞
    pthread_cond_t _empty;//空时消费者阻塞
};

小测试

我们写个多线程代码测试一下

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

void* consumer(void* args)
{
    BlockingQueue<int>* bq = static_cast<BlockingQueue<int>*> (args);
    while(true)
    {
        //1.将数据从blockqueue中获取
        int data = 0;
        bq->pop(&data);
        //2.结合某种业务逻辑,处理数据
        //这里先打印一下
        cout << "consumer data : " << data << endl;
    }
}
void* producer(void* args)
{
     BlockingQueue<int>* bq = static_cast<BlockingQueue<int>*> (args);
     while(true)
     {
        //1.生产者要先通过某种渠道获得数据,可以让用户从标准输入输入,也可以从网络里读
        //这里我们简单处理一下,自己创建随机一些数据测试一下就行
        int data = rand() % 10 + 1;
        //2.将数据推送到blockqueue,完成生产过程
        bq->push(data);
        cout << "prodecer data : " << data << endl;//打印查看
     }
}
int main()
{
    srand((uint64_t)time(nullptr) % 100000);//测试要用的数据
    //这里是为了方便理解,先写成单生产单消费
    BlockingQueue<int>* bq = new BlockingQueue<int>();
    pthread_t c,p;//c是消费者线程,p是生产者线程
    pthread_create(&c,nullptr,consumer,bq);//让消费者和生产者看到同一份队列
    pthread_create(&p,nullptr,producer,bq);

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

我们先让消费者的线程sleep(1),让它消费慢一点。然后生产者正常生产。

在这里插入图片描述
我们会发现,因为我们最开始容量最大为5,所有生产者很容易就把容器塞满了。
塞满以后就阻塞了,轮到消费者消费一个,根据我们代码所写,每次消费后就去唤醒生产者。生产者生产了,又满了。又轮到消费者消费一个,消费者又唤醒生产者。所以会出现消费一个,生产一个的情况。
很容易观察到,消费者消费的时候每次都是从队列的头获得数据的。

接下来,我们让消费者正常消费,生产者线程sleep(1),生产慢一点

void* producer(void* args)
{
     BlockingQueue<int>* bq = static_cast<BlockingQueue<int>*> (args);
     while(true)
     {
        sleep(1);
        //1.生产者要先通过某种渠道获得数据,可以让用户从标准输入输入,也可以从网络里读
        //这里我们简单处理一下,自己创建随机一些数据测试一下就行
        int data = rand() % 10 + 1;
        //2.将数据推送到blockqueue,完成生产过程
        bq->push(data);
        cout << "prodecer data : " << data << endl;//打印查看
     }
}

在这里插入图片描述
还是出现了生产一个消费一个的情况。
原因是最开始队列是空的,生产者生产慢了,消费者只能等待。等到生产者生产了一个以后,我们没加任何策略,只要生产了就唤醒消费者线程。然后消费者消费了。队列又空了,消费者又要等待生产者生产。

由这个小测试我们可以看到,我们成功地让多线程协同起来了。

细节1 线程被误唤醒的情况

现在是单生产者单消费者的情况。如果改成只有一个消费者,五个生产者,有没有可能出现生产者被误唤醒的情况?
答案是可能的。假设现在队列里的数据满了,而消费者唤醒生产者的线程不是pthread_cond_signal(),而是pthread_cond_broadcast(),一下子唤醒五个生产者。
在这里插入图片描述
这时候问题就来了,如果消费者只消费了一个数据就全部唤醒了五个生产者,这五个生产者之前都通过if语句判断通过在进行等待,唤醒时都会从箭头所指处继续执行代码。都会执行push语句,就可能超过队列的容量上限。
这只是被误唤醒的一个例子,实际中可能还要很多情况被误唤醒。所以我们就要避免这种情况。

解决方法: if语句改成while即可
被唤醒的时候再判断一下是否是满了,满了继续等待,这样就不怕被误唤醒导致继续执行下面的代码了。

    void push(const T& in)//生产者把元素放进容器
    {
        pthread_mutex_lock(&_mutex);//加锁保证线程安全
        while(isFull())//判断容器是否为满
        {
            //如果满了就进行等待
            pthread_cond_wait(&_full,&_mutex);
        }
        _q.push(in);//未满,就生产,放进容器
        //此时可以加一些策略,比如容量为多少时就唤醒,我们这里就不加了。
        pthread_cond_signal(&_empty);
        pthread_mutex_unlock(&_mutex);
    }

同理,消费者也必须改成while

    void pop(T* out)
    {
        pthread_mutex_lock(&_mutex);//加锁保证线程安全
        while(isEmpty())//判断容器是否为空
        {
            pthread_cond_wait(&_empty,&_mutex);//如果为空,消费者就进行等待
        }
        *out = _q.front();//不为空,就取队列头部元素
        _q.pop();//取出以后,队列弹出该元素
        pthread_cond_signal(&_full);
        pthread_mutex_unlock(&_mutex);
    }

细节2 生产者消费者模型高效在哪里?

在这里插入图片描述
生产者消费者模型就是生产者往容器里放元素,消费者再从容器里取元素。同时为了保证线程安全,我们还给它加锁了,所以是串行执行的,那么它高效在哪呢?

思考这几个问题:

  • 生产者是不是也需要从外部获取数据才能送到容器?
  • 消费者的数据是不是也要经过业务处理后才能送出去?
  • 生产者什么时候获取数据的时候能干嘛?
  • 消费者送出数据的时候能干嘛?

首先生产者需要从外部获取数据才能送到容器,在获取数据的同时也能把以前的数据送到容器。消费者要把处理后的数据送出去,送出去的同时也能从容器拿到新的数据。这就是生产者消费者模型高效的表现。

多生产者多消费者

我们可以接下来测试多生产多消费者的情况了,由于线程间是串行执行的,所以代码肯定是能执行的。

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

void* consumer(void* args)
{
    BlockingQueue<int>* bq = static_cast<BlockingQueue<int>*> (args);
    while(true)
    {
        sleep(1);
        //1.将数据从blockqueue中获取
        int data = 0;
        bq->pop(&data);
        //2.结合某种业务逻辑,处理数据
        //这里先打印一下
        cout << pthread_self() << " | "<<"consumer data : " << data << endl;
    }
}
void* producer(void* args)
{
     BlockingQueue<int>* bq = static_cast<BlockingQueue<int>*> (args);
     while(true)
     {
        sleep(1);
        //1.生产者要先通过某种渠道获得数据,可以让用户从标准输入输入,也可以从网络里读
        //这里我们简单处理一下,自己创建随机一些数据测试一下就行
        int data = rand() % 10 + 1;
        //2.将数据推送到blockqueue,完成生产过程
        bq->push(data);
        cout << pthread_self() << " | " << "prodecer data : " << data << endl;//打印查看
     }
}
int main()
{
    srand((uint64_t)time(nullptr) % 100000);//测试要用的数据
    //这里是为了方便理解,先写成单生产单消费
    BlockingQueue<int>* bq = new BlockingQueue<int>();
    pthread_t c1,c2,p1,p2;//c是消费者线程,p是生产者线程
    pthread_create(&c1,nullptr,consumer,bq);//让消费者和生产者看到同一份队列
    pthread_create(&c2,nullptr,consumer,bq);//让消费者和生产者看到同一份队列
    pthread_create(&p1,nullptr,producer,bq);
    pthread_create(&p2,nullptr,producer,bq);


    pthread_join(c1,nullptr);
    pthread_join(c2,nullptr);
    pthread_join(p1,nullptr);
    pthread_join(p2,nullptr);
    return 0;
}

在这里插入图片描述
在这里插入图片描述
使用ps -aL查看,包括线程在内,确实有五个线程在执行。

题外话

我们在测试的时候,只测试了int数据类型的

   BlockingQueue<int>* bq = new BlockingQueue<int>();

实际上,我们用的是一个类模板,也就说不仅仅可以传简单的数据类型,进行简单的数据处理,还可以传相应的类,类里面写你要接收的数据和处理数据的方式,然后由生产者从外界接受数据,存到对象里面,再把这个对象传给容器,消费者再拿出这个对象,根据类里面的处理数据的方式进行处理,然后在发到外界。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/770950.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

含CPU芯片的PCB可制造性设计问题详解

CPU是中央处理器&#xff0c;Central Processing Unit 英文的缩写&#xff0c;电脑中一个最重要&#xff0c;最核心的东西&#xff0c;相当一个人的大脑&#xff0c;是用来思考、分析和计算的。目前市面上比较常见的CPU来自两个品牌&#xff0c;一个是intel公司生产的&#xff…

电脑数据恢复:恢复Windows中永久删除的文件

什么是数据恢复及其工作原理&#xff1f; 一般来说&#xff0c;数据恢复是从内部和外部硬盘、固态硬盘、USB、SD卡和其他辅助存储设备中恢复无法访问、删除、丢失、损坏的数据的过程。 删除文件后&#xff0c;你的Windows操作系统会自动将不需要的文件保存到回收…

Windows系统中数据标注软件LabelImg的安装和基本使用

文章目录 前言安装LabelImgLabelImg基本使用LabelImg支持的快捷键相关链接 前言 LabelImg是国立台湾大学&#xff08;National Taiwan University&#xff09;的Tzuta Lin主导完成&#xff0c;并基于免费软件许可MIT LICENSE发布在github上的一款计算机视觉&#xff08;Comput…

Ansible自动化运维工具的认识

目录 一、Ansible概述 二、Ansible特点 三、Ansible应用 1、使用者 2、Ansible工具集合 3、作用对象 四、Ansible的搭建 1、实验环境 2、环境准备 Ansible&#xff1a; 3、创建ssh免密交互登录 client端环境准备 五、Ansible配置 六、Ansible命令 1、ansible 实…

【C++ 重要知识点总结】表达式

表达式 1 基础 组合运算 优先级结合律 类型转换 运算符重载 左值和右值 2 算数运算符 3 逻辑和关系运算法 短路求值 逻辑与&#xff0c;当第一个判定为否的时候&#xff0c;不再执行第二个判定&#xff0c;可以用来屏蔽第二步的计算&#xff0c;代替条件判断&#xff0…

为什么很多公司都开始使用Go语言了?

越来越多的互联网大厂开始使用Go语言了&#xff0c;譬如腾讯、美团、滴滴、百度、Google、bilibili... 还有最初使用Python的字节跳动&#xff0c;甚至已经全面拥向Go了。这么多国内外首屈一指的公司&#xff0c;都在开始使用它了&#xff0c;它到底有什么优势呢&#xff1f;这…

03.MySQL——索引和事务

索引 索引的概念 索引可以提高数据库的性能。不用加内存&#xff0c;不用改程序&#xff0c;不用调sql&#xff0c;只要执行正确的 create index &#xff0c;查询速度就可能提高成百上千倍。但是查询速度的提高以插入、更新、删除的速度为代价。索引的价值在于提高一个海量数…

SOT封装特点和优势,sot23封装尺寸

SOT封装是一种常用的集成电路封装类型&#xff0c;常见的SOT封装类型包括3引脚&#xff08;如SOT-23&#xff09;、4引脚&#xff08;如SOT-89和SOT-223&#xff09;和6引脚&#xff08;如SOT-363&#xff09;&#xff0c;可以适应不同的电路设计和功能要求。具有以下特点和优势…

Springboot配置相关问题

目录 一.ConfigurationProperties注解补充知识&#xff1a; 二、松散绑定三、常用计量单位的应用四、数据校验补充知识 一.ConfigurationProperties注解 使用该注解可以为Bean绑定application.yml中的属性值。以下就是使用ConfigurationProperties注解的示例 项目结构 Serve…

Halcon机器视觉-15种常用缺陷检测实例

一、Halcon 15种常用缺陷检测实例分享 缺陷检测是一种通过计算机视觉技术来检测产品制造过程中的缺陷的方法。该技术可以检测出产品表面的缺陷&#xff0c;如裂纹、凹陷、划痕、气泡等&#xff0c;并且可以实时监测和诊断制造过程中的问题。在制造业中&#xff0c;机器视觉缺陷…

spring复习:(45)使用TransactionProxyFactoryBean来实现事务时,发生异常时,事务是怎么回滚的?

TransactionAspectSupport类&#xff1a; invokeWithinTransaction方法发生异常时会调用completeTransactionAfterThrowing protected void completeTransactionAfterThrowing(Nullable TransactionInfo txInfo, Throwable ex) {if (txInfo ! null && txInfo.getTrans…

Java Mybatis拓展03

0目录 1.MyBatis当实体类和数据库字段名不对应 2.多表查询 1.MyBatis当实体类和数据库字段名不对应 方法2 测试 多表查询 加入子标签association 模糊查询 加入Address 对象 三表联查 2.五表联查 测试

微服务Day3——Nacos配置管理\Feign远程调用\Gateway网关

一、Nacos配置管理 1、统一配置管理 当微服务部署的实例越来越多&#xff0c;达到数十、数百时&#xff0c;逐个修改微服务配置就会让人抓狂&#xff0c;而且很容易出错。我们需要一种统一配置管理方案&#xff0c;可以集中管理所有实例的配置。 Nacos一方面可以将配置集中管理…

朝花夕拾 - 2023 精神错乱记录

jsliang 的精神错乱记录&#xff0c;一点 2023 小思考。 也许我们曾偏离航道&#xff0c;但请不要放弃抵达终点 前言 在 2020.11 过来珠海&#xff0c;来到金山工作 2 年半的时间里&#xff1a; 在工作上&#xff0c;更换了 3 个小团队&#xff0c;达到了每年一换在工作上&…

我国版式文档格式OFD前端WEB展示之EasyOFD

EasyOFD an ofd file web shower 一个在web端展示ofd文件的控件&#xff0c;该控件基于CANVAS绘制。 该控件使用了以下外部程序 1&#xff09;jszip&#xff1a;解决解压文件。 2&#xff09;x2js: 解决XML文件到JS转换 3&#xff09;easyjbig2: 解决ofd内部使用jb2文件存储的…

A Survey on Time-Series Pre-Trained Models

本文是LLM系列的文章&#xff0c;针对《A Survey on Time-Series Pre-Trained Models》的翻译。 时间序列预训练模型综述 摘要1 引言2 背景2.1 时间序列挖掘任务2.1.1 时间序列分类2.1.2 时间序列预测2.1.3 时间序列聚类2.1.4 时间序列异常检测2.1.5 时间序列推测 2.2 深度学习…

手打 小份 kubernetes v1.27.3 集群

文章目录 1. 准备2. yum3. 安装 ansible4. 互信5. hosts6. 关闭防火墙、swap、selinux7. 配置系统文件句柄数8. 启用ipvs9. 修改内核参数10. 安装 containerd11. 安装nerdctl12. kubernetes yum13. 部署 kubernetes13.1 安装工具13.2 初始化配置 14. 部署 master15. 部署 node1…

【微信机器人开发

现在并没有长期免费的微信群机器人&#xff0c;很多都是前期免费试用&#xff0c;后期进行收费&#xff0c;或者核心功能需要付费使用的。 这时如果需要群机器人帮助我们管理群聊&#xff0c;建议大家使有条件的可以自己开发微信管理系统。了解微信群机器人的朋友都知道&#x…

iTerm复制粘贴出现00~ 01~

问题 iTerm2中复制粘贴出现如下现象 解决 命令行直接输入printf \e[?2004l 回车

SSH跳转/SCP复制远程目标服务器的高阶使用

在日常开发和运维的过程中&#xff0c;我一般是使用Xshell的工具对linux服务器的相关操作。我说一下我写这篇文章的背景&#xff1a;甲方因为安全需要&#xff0c;给了一台可以通过vpn访问的跳板机&#xff0c;通过这台跳板机去操作另外的十多台应用服务器&#xff0c;那么肯定…