【Linux线程(三)】生产者消费者模型

目录

前言:

一、什么是生产者消费者模型

(一)概念

(二)321原则

1.三个关系

2. 两种角色

3.一个场所

(三)生产者消费者模型的优缺点

二、基于阻塞队列实现生产者消费者模型

(一)介绍

(二)代码实现

BlockQueue.hpp

LockGuard.hpp

Task.hpp

 Main.cc

三、POSIX信号量

四、基于环形队列实现生产者消费者模型

(一)原理

(二)代码实现

RingQueue.hpp

Task.hpp

Main.cc

 (五)总结


前言:

在之前的学习中,我们了解到在多线程中往往会存在很多问题,比如竞态条件、死锁、数据竞争等问题,我们可以用互斥和同步来解决这些问题。

互斥和同步是一种具体的实现方法,它侧重于线程之间对共享资源的安全访问,今天我们学习一种抽象模型:生产者消费者模型。生产者消费者模型更关注于解决整体的生产者和消费者之间的协作问题。

一、什么是生产者消费者模型

(一)概念

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

生产者的主要任务是生成一些数据,并将其放入共享的缓冲区中。消费者的任务则是从缓冲区中取出数据并进行处理。这种模型的目标是确保生产者和消费者之间的协调,以避免出现数据竞争或缓冲区溢出等问题。

在实现生产者-消费者模型时,通常会使用一些同步机制,比如信号量、互斥锁或条件变量等,来确保生产者和消费者之间的正确协作。

(二)321原则

将生产者消费者模型抽象成一个超市、顾客和供应商

供应商和顾客之间不需要直接接触,通过超市就能实现资源的共享。

1.三个关系

供应商和供应商之间需要维持互斥:

向超市中放入资源时,会有多个供应商同时放入,这会导致资源覆盖或丢失、缓冲区溢出等问题,因此需要保证同一时刻只能有一个供应商向超市中放入资源。

顾客和顾客之间需要维持互斥:

跟供应商之间的关系相同,可能会有多个顾客同时从超市中取出资源。

供应商和顾客之间需要维持互斥和同步:

  • 因为供应商和顾客都需要进入超市,所以需要互斥机制使得同一时刻只能有一方进入。
  • 由于顾客必须等待供应商先向超市中放入资源才能取出资源,所以供应商和顾客之间形成了先后的顺序关系,这种关系需要利用同步机制来维持。

2. 两种角色

两种角色指的是生产者和消费者这两个角色,在我们的超市中就是供应商和顾客。

3.一个场所

一个场所指的是存放共享资源的缓冲区,超市就是这个场所。

生产者消费者模型可以用321原则来描述,通过321原则我们可以更好的理解生产者消费者模型并更容易对其编程。

(三)生产者消费者模型的优缺点

优点:

  1. 解耦合:生产者和消费者之间通过共享缓冲区进行通信,使得它们的实现相对独立。这种解耦合使得系统更易于扩展和维护。

  2. 资源利用率高:生产者和消费者可以并行执行,从而充分利用系统资源,提高系统的效率。

  3. 平衡生产和消费速率:通过合理设计缓冲区的大小和同步机制,可以平衡生产者和消费者之间的速率,避免生产者速度过快导致消费者无法处理所有数据,或者消费者速度过快导致资源浪费的问题。

  4. 简化并发编程:生产者-消费者模型提供了一种结构化的方法来管理并发编程中的数据共享与同步,简化了并发编程的复杂性。

缺点:

  1. 同步开销:实现生产者-消费者模型通常需要使用同步机制来保证生产者和消费者之间的正确协作,这可能引入额外的同步开销,影响系统的性能。

  2. 死锁和饥饿:如果同步机制设计不当,可能会导致死锁或者某些线程长时间得不到执行的饥饿问题。

  3. 缓冲区大小限制:生产者-消费者模型中的缓冲区大小需要事先确定,如果缓冲区过小,可能会导致生产者或者消费者无法及时处理数据;如果缓冲区过大,可能会占用过多的系统资源。

  4. 复杂性增加:虽然生产者-消费者模型简化了并发编程中的一些问题,但是对于初学者来说,理解和正确实现该模型仍然是有一定挑战的。

总的来说,生产者-消费者模型在适当的场景下是非常有用的,并且可以有效地提高系统的性能和可维护性,但是在使用时需要注意合理设计同步机制和缓冲区大小,以及避免死锁和饥饿等问题。

二、基于阻塞队列实现生产者消费者模型

(一)介绍

我们今天利用阻塞队列来充当生产者和消费者之间的缓冲区。阻塞队列是一种特殊类型的队列,它支持当队列为空时阻塞消费者队列,当队列为满时阻塞生产者队列。利用这个队列,我们可以有效的管理生产者和消费者之间的数据共享和同步,简化并发编程。

(二)代码实现

由于阻塞队列需要我们自己创建,我们就创建一个hpp文件用来编写阻塞队列,用vector实现其主体,利用信号量来实现阻塞。

BlockQueue.hpp

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



const int defaultcap = 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)  //给生产者的
    {
        LockGurad lock(&_mutex);
        //pthread_mutex_lock(&_mutex);
        while(IsFull())
        {
            //阻塞等待
            pthread_cond_wait(&_p_cond,&_mutex);
        }
        _q.push(in);
        pthread_cond_signal(&_c_cond);
        //pthread_mutex_unlock(&_mutex);        
    }
    void Pop(T *out)  // 给消费者的
    {
        LockGurad lock(&_mutex);
        //pthread_mutex_lock(&_mutex);
        while(IsEmpty())
        {
            //阻塞等待
            pthread_cond_wait(&_c_cond,&_mutex);
        }
        *out = _q.front();
        _q.pop();
        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; // 给消费者的

    // int consumer_water_line = _capacity / 3 * 2;  // 消费者水位线,当水位线上升到三分之二时就开始消费
    // int productor_water_line = _capacity / 3;  // 生产者水位线,当水位线下降到三分之一时就开始生产
};

在阻塞队列中,我们将锁封装为LockGuard,用互斥锁实现互斥机制。

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 LockGurad
{
public:
    LockGurad(pthread_mutex_t *lock):_mutex(lock)
    {
        _mutex.Lock();
    }
    ~LockGurad()
    {
        _mutex.Unlock();
    }
private:
    Mutex _mutex;
};

我们让生产者生产任务并放入队列,让消费者拿取任务并实行,任务内容为一个算术表达式。

Task.hpp

#pragma once
#include <iostream>

const int defaultvalue = 0;
std::string opers = "+-*/%)(";

enum
{
    ok = 0,
    div_zero,
    mod_zero,
    unknow
};
class Task
{
public:
    Task()
    {
    }
    Task(int x, int y, char op)
        : data_x(x), data_y(y), oper(op), result(defaultvalue), code(ok)
    {
    }
    void Run()
    {
        switch (oper)
        {
        case '+':
            result = data_x + data_y;
            break;
        case '-':
            result = data_x - data_y;
            break;
        case '*':
            result = data_x * data_y;
            break;
        case '/':
        {
            if (data_y == 0)
                code = div_zero;
            else
                result = data_x / data_y;
        }
        break;
        case '%':
        {
            if (data_y == 0)
                code = div_zero;
            else
                result = data_x / data_y;
        }

        break;
        default:
            code = unknow;
            break;
        }
    }
    std::string PrintTask()
    {
        std::string s;
        s = std::to_string(data_x);
        s += oper;
        s += std::to_string(data_y);
        s += "=?";

        return s;
    }
    std::string PrintResult()
    {
        std::string s;
        s = std::to_string(data_x);
        s += oper;
        s += std::to_string(data_y);
        s += "=";
        s += std::to_string(result);
        s += "[";
        s += std::to_string(code);
        s += "]";

        return s;
    }
    ~Task()
    {
    }

private:
    int data_x;
    int data_y;
    char oper;

    int result;
    int code;
};

 Main.cc

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

class ThreadData
{
public:
    BlockQueue<Task> *bq;
    std::string name;
};

void *consumer(void *args)
{
    ThreadData *td = (ThreadData *)args;
    // BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);
    while (true)
    {
        // sleep(1);
        Task t;
        // 1.消费数据  bq->Pop(&data)

        td->bq->Pop(&t);
        t.Run();
        // 2.进行处理
        std::cout << "consumer data:" << t.PrintResult() << ", " << td->name << std::endl;
    }

    return nullptr;
}
void *productor(void *args)
{
    BlockQueue<Task> *bq = static_cast<BlockQueue<Task> *>(args);
    while (true)
    {
        // 1.有数据
        int data1 = rand() % 10;
        usleep(rand() % 123);
        int data2 = rand() % 10;
        usleep(rand() % 123);
        char oper = opers[rand() % (opers.size())];
        Task t(data1, data2, oper);
        std::cout << "productor data:" << t.PrintTask() << std::endl;

        // 2.进行生产
        // bq->push(&data);
        bq->Push(t);

        sleep(1);
    }
    return nullptr;
}
int main()
{
    srand((uint64_t)time(nullptr) ^ getpid() ^ pthread_self());
    BlockQueue<Task> *bq = new BlockQueue<Task>();
    pthread_t c[3], p[2]; // 消费者和生产者

    ThreadData *td1 = new ThreadData();
    td1->bq = bq;
    td1->name = "thread-1";
    pthread_create(&c[0], nullptr, consumer, td1);

    ThreadData *td2 = new ThreadData();
    td2->bq = bq;
    td2->name = "thread-2";
    pthread_create(&c[1], nullptr, consumer, td2);

    ThreadData *td3 = new ThreadData();
    td3->bq = bq;
    td3->name = "thread-3";
    pthread_create(&c[2], nullptr, consumer, td3);

    pthread_create(&p[0], nullptr, productor, bq);
    pthread_create(&p[1], nullptr, productor, bq);

    pthread_join(c[0], nullptr);
    pthread_join(c[1], nullptr);
    pthread_join(c[2], nullptr);
    pthread_join(p[0], nullptr);
    pthread_join(p[1], nullptr);


    return 0;
}

三、POSIX信号量

http://t.csdnimg.cn/mhTLS

在上面这篇博客中,我已经介绍了System V信号量,今天要讲的是POSIX信号量。

POSIX(Portable Operating System Interface)信号量是一种同步原语,用于在多线程或多进程环境中实现进程之间的同步与互斥。POSIX标准定义了一组API,使得程序员可以在各种UNIX-like操作系统上使用相同的信号量接口。


 

       #include <semaphore.h>

       int sem_init(sem_t *sem, int pshared, unsigned int value);

作用:

sem_init是POSIX信号量的初始化函数,用于初始化一个新的信号量对象。它的作用是创建或者初始化一个新的POSIX信号量,并设置信号量的初始值。

参数:

  • sem: 指向要初始化的信号量对象的指针。
  • pshared: 指定信号量的类型,可以是 0 表示信号量在进程内部共享,也可以是 1 表示信号量在进程间共享。
  • value: 指定信号量的初始值,即初始时信号量的计数值。

返回值:

  • 如果初始化成功,则返回 0
  • 如果出现错误,则返回 -1,并设置 errno 来指示具体的错误原因。
       #include <semaphore.h>

       int sem_destroy(sem_t *sem);

作用:

sem_destroy函数用于销毁一个已经初始化的POSIX信号量对象。它的作用是释放由sem_init创建的信号量对象所占用的资源,并将该信号量对象恢复到未初始化状态。

参数:

  • sem: 指向要销毁的信号量对象的指针。

返回值:

  • 如果销毁成功,则返回 0
  • 如果出现错误,则返回 -1,并设置 errno 来指示具体的错误原因。
       #include <semaphore.h>

       int sem_wait(sem_t *sem);

作用:

sem_wait函数用于等待一个信号量,并在信号量的值大于0时将其递减。如果信号量的值大于0,则sem_wait会将信号量的值减1,并立即返回;如果信号量的值为0,则sem_wait会阻塞当前线程,直到信号量的值大于0为止。

参数:

  • sem: 指向要等待的信号量对象的指针。

返回值:

  • 如果等待成功(即信号量的值大于0),则返回 0
  • 如果出现错误,则返回 -1,并设置 errno 来指示具体的错误原因。

 

       #include <semaphore.h>

       int sem_post(sem_t *sem);

作用:

sem_post函数用于增加(释放)一个信号量的值。它的作用是增加信号量的值,并唤醒一个等待该信号量的线程(如果有的话),以通知其继续执行

参数:

  • sem: 指向要增加值的信号量对象的指针。

返回值:

  • 如果增加成功,则返回 0
  • 如果出现错误,则返回 -1,并设置 errno 来指示具体的错误原因。

四、基于环形队列实现生产者消费者模型

(一)原理

在之前对共享资源的理解中,我们潜意识把共享资源看成了一个整体,那可不可以把共享资源拆成很多部分,让信号量去管理这些共享资源块,每有一个线程来申请信号量,就是将这个共享资源块分给这个线程,当所有的共享资源块都被使用了,信号量也就为0,不允许再次申请了。

这种方式称为信号量的计数模式。每个信号量计数的单位可以被看作是一个共享资源块。当一个线程需要访问共享资源时,它会申请一个信号量计数单位,如果所有的计数单位都被使用了,信号量的值就会为0,进而阻塞其他线程的申请操作,直到有其他线程释放了信号量。

利用这个理解,我们可以利用环形队列和信号量来实现生产者消费者模式。

(二)代码实现

首先我们需要创建环形队列,我们需要分别记录生产者和消费者在队列中的位置以及队列的大小,而生产者需要的资源是空间资源,消费者需要数据资源。生产者需要先对空间信号量进行P操作,然后写入数据后,对数据信号量进行V操作;而消费者则相反。所以还需要两个信号量,一个是空间信号量,一个是数据信号量。

RingQueue.hpp

#pragma once
#include<iostream>
#include<pthread.h>
#include<vector>
#include<semaphore.h>

using namespace std;

const int defaultsize = 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 size = defaultsize)
    :_ringqueue(size),_size(size),_p_step(0),_c_step(0)
    {
        sem_init(&_space_sem,0,size);
        sem_init(&_data_sem,0,0);

        pthread_mutex_init(&_c_mutex);
        pthread_mutex_init(&_p_mutex);
    }
    void Push(const T &in)
    {
        // 生产
        // 先加锁,还是先申请信号量?
        pthread_mutex_lock(&_p_mutex);
        P(_space_sem);
        _ringqueue[_p_step] = in;
        _p_step++;
        _p_step %= _size;
        V(_data_sem);
        pthread_mutex_unlock(&_p_mutex);

    }
    void Pop(T *out)
    {
        // 消费
        pthread_mutex_lock(&_c_mutex);
        P(_data_sem);
        *out = _ringqueue[_c_step];
        _c_step++;
        _c_step %= _size;
        V(_space_sem);
        pthread_mutex_lock(&_c_mutex);

    }
    ~RingQueue()
    {
        sem_destroy(&_space_sem);
        sem_destroy(&_data_sem);

        pthread_mutex_destroy(_c_mutex);
        pthread_mutex_destroy(_p_mutex);

    }
private:
    vector<T> _ringqueue;    
    int _size;

    int _p_step;  // 生产者的生产位置
    int _c_step;  // 消费者的消费位置
    
    sem_t _space_sem;  // 生产者
    sem_t _data_sem;   // 消费者

    pthread_mutex_t _c_mutex;
    pthread_mutex_t _p_mutex;
};

生产者向队列中写入的数据我们就用之前阻塞队列中的任务

Task.hpp

#pragma once
#include <iostream>

const int defaultvalue = 0;
std::string opers = "+-*/%)(";

enum
{
    ok = 0,
    div_zero,
    mod_zero,
    unknow
};
class Task
{
public:
    Task()
    {
    }
    Task(int x, int y, char op)
        : data_x(x), data_y(y), oper(op), result(defaultvalue), code(ok)
    {
    }
    void Run()
    {
        switch (oper)
        {
        case '+':
            result = data_x + data_y;
            break;
        case '-':
            result = data_x - data_y;
            break;
        case '*':
            result = data_x * data_y;
            break;
        case '/':
        {
            if (data_y == 0)
                code = div_zero;
            else
                result = data_x / data_y;
        }
        break;
        case '%':
        {
            if (data_y == 0)
                code = div_zero;
            else
                result = data_x / data_y;
        }

        break;
        default:
            code = unknow;
            break;
        }
    }
    std::string PrintTask()
    {
        std::string s;
        s = std::to_string(data_x);
        s += oper;
        s += std::to_string(data_y);
        s += "=?";

        return s;
    }
    std::string PrintResult()
    {
        std::string s;
        s = std::to_string(data_x);
        s += oper;
        s += std::to_string(data_y);
        s += "=";
        s += std::to_string(result);
        s += "[";
        s += std::to_string(code);
        s += "]";

        return s;
    }
    ~Task()
    {
    }

private:
    int data_x;
    int data_y;
    char oper;

    int result;
    int code;
};

Main.cc

#include "RingQueue.hpp"
#include "Task.hpp"
#include <pthread.h>
#include <unistd.h>

void *Productor(void *args)
{
    RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);

    while (true)
    {
        // 数据怎么来的?
        // 1.有数据
        int data1 = rand() % 10;
        //usleep(rand() % 123);
        int data2 = rand() % 10;
        //usleep(rand() % 123);
        char oper = opers[rand() % (opers.size())];
        Task t(data1, data2, oper);
        std::cout << "productor data:" << t.PrintTask() << std::endl;

        rq->Push(t);

        //sleep(1);
    }
}
void *Consumer(void *args)
{
    RingQueue<Task> *rq = static_cast<RingQueue<Task> *>(args);

    while (true)
    {
        Task t;
        rq->Pop(&t);

        t.Run();

        cout<<"consumer done,data is :"<<t.PrintResult()<<endl;

    }
}
int main()
{
    srand((uint64_t)time(nullptr)^pthread_self());
    pthread_t c, p;

    RingQueue<Task> *rq = new RingQueue<Task>();
    pthread_create(&c, nullptr, Consumer, rq);
    pthread_create(&c, nullptr, Productor, rq);

    pthread_join(c, nullptr);
    pthread_join(p, nullptr);

    return 0;
}

 (五)总结

这样我们基于阻塞队列和环形队列实现了生产者消费者模型,它们的基本思想就是将共享资源分成多个部分的方式,也就是信号量的计数模式,该模式有以下优点:

  1. 更细粒度的控制:可以更灵活地控制对共享资源的访问,避免了对整个共享资源的串行访问。
  2. 提高并发性:允许多个线程同时访问不同的共享资源块,提高了并发性和吞吐量。
  3. 减小竞争:由于资源被分割成多个部分,不同的线程可以独立地访问不同的资源块,减小了竞争的可能性。

环形队列并不一定会优于阻塞队列,我们需要根据不同的环境选择不同的实现方式,才能提高程序的效率。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

算法练习第22天|39. 组合总和、40.组合总和II

39. 组合总和 39. 组合总和 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/combination-sum/description/ 题目描述&#xff1a; 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数…

安卓、iOS、iPad三端搞定,不再剧荒!

哈喽&#xff0c;各位小伙伴们好&#xff0c;我是给大家带来各类黑科技与前沿资讯的小武。 之前给大家推荐过各种看剧姿势&#xff0c;但很多苹果、平板端的小伙伴还是存在更好的需求体验&#xff0c;今天给大家推荐这款可以在安卓、iOS和平板上都能安装使用&#xff0c;不再剧…

合并K个升序链表

题目 解法一 优先级队列 思想 将每个链表中的一个节点存放到优先级队列中&#xff0c;本题采用小根堆&#xff0c;将小根堆中的根节点取出&#xff0c;插入到最终的链表中&#xff0c;并且将该节点在原链表中的下一个节点插入小根堆中&#xff08;需要向下调整&#xff09;&a…

【OpenHarmony IDL工具规格及使用说明书】

OpenHarmony IDL工具规格及使用说明书 IDL接口描述语言简介 当客户端和服务器进行IPC通信时&#xff0c;需要定义双方都认可的接口&#xff0c;以保障双方可以成功通信&#xff0c;OpenHarmony IDL&#xff08;OpenHarmony Interface Definition Language&#xff09;则是一种…

GPU学习记一下线程分组相关

在compute的时候&#xff0c;是要dispatch一个数量的代表分了多少块任务集&#xff0c;dispatch的块内部也是有一个数量的&#xff0c;那么这些值怎么取的呢 内部&#xff0c;N卡32 外面dispatch的数量就是all/32 然后细说这个值 这有一个叫core的东西&#xff0c;就是相当于th…

惠普打印机无线网络连接设置

休息一下&#xff0c;灌个水。这次没多少内容&#xff0c;具体步骤惠普官网上都有&#xff0c;唯一增加的是对安装过程中踩的坑做了一个说明。 一&#xff0e;打印机无线网络连接设置步骤 惠普打印机设置无线网络连接&#xff0c;共16个步骤。 1. 在电脑上打开任意浏览器&am…

微信原生小程序-图片上传回显(含组件封装详解)

实现效果&#xff08;如图所示&#xff09;&#xff1a;点击上传>弹出拍摄或从手机相册中选择图片或视频>选择图片上传>上传成功后回显图片。 文件梳理分析&#xff08;注意点&#xff09;&#xff1a; index文件表示当前页面文件&#xff0c;case-upload-item文件表示…

Java学习48-Java 流(Stream)、文件(File)和IO - 复习章节

1.File类的使用 File类的一个实例对应着磁盘上的文件或文件目录。(必须熟悉)File的实例化(新建一个对象)&#xff0c;常用的方法File类中只有新建&#xff0c;删除&#xff0c;获取路径等方法&#xff0c;不包含读写文件的方法&#xff0c;此时需要使用使用下面说的IO流 IO流…

STL 总结

STL 在 C 标准模板库&#xff08;STL&#xff09;中&#xff0c;主要包含了一系列的容器、迭代器、算法、函数对象、适配器。 容器 容器是用于存储数据的类模板。STL 容器可以分为序列型容器、关联型容器和链表型容器三类&#xff1a;序列型容器&#xff1a;vector、deque、…

XMind 头脑风暴/思维导图软件_V24.04.10291 PC高级版

一款风靡全球的头脑风暴和思维导图软件&#xff0c;为激发灵感和创意而生。在国内使用广泛&#xff0c;拥有强大的功能&#xff0c;包括思维管理&#xff0c;商务演示&#xff0c;与办公软件协同工作等功能。XMind中文版采用全球先进的Eclipse RCP软件架构&#xff0c;是集思维…

QT状态机8-使用恢复策略自动恢复属性

当状态分配的属性不再活动时,可能希望将其恢复到初始值,通过设置全局的恢复策略可以使状态机进入一个状态而不用明确制定属性的值。 QStateMachine machine; machine.setGlobalRestorePolicy(QStateMachine::RestoreProperties);当设置了恢复策略以后,状态机将自动恢复所有…

k8s pod就绪探针

Pod 可能需要时间来加载配置或数据&#xff0c;或者可能需要执行预热过程以防止第一个用户请求时间太长影响了用户体验。在这种情况下&#xff0c;不希望该 pod 立即开始接收请求&#xff0c;尤其是在运行的实例可以正确快速地处理请求的情况下。不要将请求转发到正在启动的 po…

第十四届蓝桥杯大赛软件赛国赛C/C++ 大学 B 组 数三角

//枚举顶点。 //不存在等边三角形 #include<bits/stdc.h> using namespace std; #define int long long const int n2e311; int a,b,c,l[n],r[n]; signed main() {ios::sync_with_stdio(false);cin.tie(0),cout.tie(0);cin>>a;for(int i1;i<a;i){cin>>…

欢迎光临Java中的客“栈”

就目前而言&#xff0c;相信大家对数组、链表还有栈都基本已经有了一些了解&#xff0c;本篇文章将以栈为主体&#xff0c;探究栈和数组&#xff0c;栈和链表之间的一些联系。 当然在开始对栈的学习之前&#xff0c;我们先回顾有关数组、链表的基础知识点。 学习代码就是一个…

抛弃Elasticsearch ,MeiliSearch 从入门到入门,现在不精通

Elasticsearch 做为老牌搜索引擎&#xff0c;功能基本满足&#xff0c;但复杂&#xff0c;重量级&#xff0c;适合大数据量。 MeiliSearch 设计目标针对数据在 500GB 左右的搜索需求&#xff0c;极快&#xff0c;单文件&#xff0c;超轻量。 所以&#xff0c;对于中小型项目来说…

2024年,诺基亚手机发售仅一天就售罄

在智能手机越来越同质化的今天&#xff0c;各家都只卷性能和相机&#xff0c;大火的 AI 对于咱来说好像实用性又不太大&#xff0c;机圈属实整的有点儿无聊。 不过在阿红这两天上网冲浪的时候&#xff0c;一个陌生又熟悉的名字闯入了我的视线&#xff0c;——诺基亚&#xff08…

VMware Workstation 安装CentOS Linux操作系统

1.我们已经下载好VMware 创建新的虚拟机 2.选择典型 3.安装程序光盘映像文件 4.配置用户名密码 5.命名虚拟机&#xff0c;并确定位置 6.如图所示设置 7.等待&#xff08;时间会有点久&#xff09; 8.输入密码登入账号

##20 实现图像风格迁移:使用PyTorch深入学习的艺术之旅

文章目录 前言项目概述准备阶段图像处理模型选择风格和内容特征提取风格迁移算法优化过程结果展示完整代码与实验项目结论参考文献 前言 图像风格迁移是一种使一幅图像呈现另一幅画作风格的技术&#xff0c;通过深度学习&#xff0c;我们能够捕捉到内容图像的结构信息和风格图…

海外媒体发稿:如何在日本媒体投放新闻通稿-大舍传媒

导言 在全球化的时代背景下&#xff0c;海外媒体宣发对于企业来说非常重要。通过在海外媒体投放新闻通稿&#xff0c;企业能够拓展海外市场&#xff0c;增强知名度和影响力。本文将探讨如何在海外媒体投放新闻通稿&#xff0c;以帮助企业进行有效的海外宣传。 挖掘海外媒体资…

Java后端面试常见问题

Java后端面试 经历了两个月的面试和准备&#xff0c;下面对常见的八股文进行总结。有些问题是网上看到的面经里提到的&#xff0c;有些是我真实面试过程遇到的。 异常 1、异常分为哪几种&#xff1f;他们的父类是什么&#xff1f; 注意&#xff1a;所有异常对象的父类为Thr…