【Linux 22】生产者消费者模型

文章目录

  • 🌈 一、生产者消费者模型
    • ⭐ 1. 生产者消费者模型的概念
    • ⭐ 2. 生产者消费者模型的特点
    • ⭐ 3. 生产者消费者模型的优点
  • 🌈 二、基于阻塞队列的生产消费模型
    • ⭐ 1. 阻塞队列概念
    • ⭐ 2. 模拟实现基于阻塞队列的生产消费模型
  • 🌈 三、POSIX 信号量
    • ⭐ 1. POSIX 信号量概念
      • 🌙 1.1 信号量的本质
      • 🌙 1.2 信号量的 PV 操作
    • ⭐ 2. POSIX 信号量函数
      • 🌙 2.1 初始化信号量
      • 🌙 2.2 销毁信号量
      • 🌙 2.3 申请信号量 (等待信号量)
      • 🌙 2.4 释放信号量 (发布信号量)
  • 🌈 四、基于环形队列的生产消费模型
    • ⭐ 1. 环形队列的概念
      • 🌙 1.1 空间资源和数据资源
    • ⭐ 2. 环形队列的规则
      • 🌙 2.1 生产者和消费者不能访问同一个位置
      • 🌙 2.2 生产者不能快消费者一圈以上
      • 🌙 2.3 消费者不能超过生产者
    • ⭐ 3. 生产者消费者并发访问环形队列的场景
    • ⭐ 4. 模拟实现基于环形队列的生产消费模型

🌈 一、生产者消费者模型

⭐ 1. 生产者消费者模型的概念

  • 生产者消费者模式通过一个容器来解决生产者和消费者之间的强耦合问题。
  • 生产者和消费者之间不会直接通讯,而是通过这个容器来进行通讯。
  • 生产者生产完数据之后不用等消费者处理,而是直接将生产出的数据放到这个容器,消费者也不找生产者要数据,而是直接从这个容器里取。
    • 简单理解成 供应商 → 超市 → 消费者 就行。
  • 一般是使用阻塞队列 / 环形队列作为生产者和消费者之间通讯的容器,这个容器相当于一个缓冲区,平衡了生产者和消费者的处理能力。
  • 这个阻塞队列就是专门用来给生产者和消费者解耦的。

image-20240917211817739

⭐ 2. 生产者消费者模型的特点

1. 生产者与消费者之间的 321 原则

  • 3 种关系:生产者和生产者 (互斥关系)、消费者和消费者 (互斥关系)、生产者和消费者 (互斥 / 同步关系) 。
  • 2 种角色:生产者和消费者 (通常由 进程 / 线程 充当这两种角色) 。
  • 1 个场所:生产者和消费者交易的场所,通常指的是内存中的一段缓冲区,也能自己通过某种方式组织起来。

2. 为什么生产者和消费者、消费者和消费者、生产者和生产者之间会存在互斥关系

  • 生产者和消费者:生产者在放数据时不知道自己有没有放完数据,消费者在拿数据时也不知道自己有没有拿到数据,如果不互斥,在访问同一个位置时就可能产生数据不一致的问题,因此需要竞争互斥锁。

  • 生产者和生产者:在访问同一个位置时,可能生产者 A、B 都认为这个位置没东西,就会都往这里放数据,会导致先放的数据会被后放的数据覆盖。

  • 消费者和消费者:消费者 A、B 同时访问一个位置,都认为这个位置有数据,但 B 先把东西拿走了,A 不知道数据已经被取走了,继续去拿数据,导致拿了个寂寞。

3. 生产者和消费者之间为什么会存在同步关系

  • 如果让生产者一直生产数据,当容器被数据塞满后,生产者再进行生产就会导致生产失败。
  • 如果让消费者一直拿取数据,当容器数据被搬空后,消费者再进行消费就会导致消费失败。
  • 虽然,一直生产或一直消费不会导致数据不一致的问题,但却会引发饥饿问题。
  • 应该让生产者和消费者之间在访问容器时具有一定的顺序性。
    • 当容器的管理者发现容器内数据量下降到标准线之下时,就让消费者停止消费,通知生产者生产数据。
    • 当容器中的数据量被填充到标准线之上时,就让生产者停止生产,通知消费者过来消费。

4. 互斥保证数据的准确性,同步将多线程协同起来,两者并不冲突

⭐ 3. 生产者消费者模型的优点

1. 解耦

  • 生产者只负责生产数据,消费者只负责消费数据,两者之间互不影响。

  • 从代码层面看,生产者线程和消费者线程的代码并不直接互相调用,两者的代码在发生变化不会对对方造成影响。

2. 支持并发

  • 在消费者从缓冲区拿取数据后的处理数据期间,生产者可以同时进行生产对缓冲区添加数据。
  • 如果没有缓冲区,消费者得直接去找生产者要数据,就必须等待生产真产生数据,同理,生产者也需要等待消费者消费数据。

3. 支持忙闲不均

  • 在缓冲区未满时,生产者和消费者互不影响,不会产生占用 CPU 时间片的问题;
  • 在缓冲区已满时,生产者不再生产数据,在缓冲区空时,消费者不再消费数据,使得两者总体处于一种动态平衡的状态。

🌈 二、基于阻塞队列的生产消费模型

⭐ 1. 阻塞队列概念

  • 在多线程中,==阻塞队列(Blocking Queue)==是一种常用于实现生产者消费者模型的数据结构。

image-20240919203925657

阻塞队列和一般队列的区别

  • 当阻塞队列为空时,消费者线程从阻塞队列中获取元素的操作会被阻塞,直到阻塞队列中被生产者线程放入元素为止;
  • 当阻塞队列为满时,生产者线程往阻塞队列中存放数据的操作会被阻塞,直到阻塞队列因为被消费者线程拿走元素而出现空位为止。

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

  • 根据 C++ 中的 queue 容器实现一个阻塞队列。
  • 为了方便演示,实现的是一个单生产者、单消费者的模型。

image-20240919205630182

  • 当生产者线程把阻塞队列填满时,通知消费者线程消费;当消费者线程把阻塞队列搬空时,通知生产者线程生产。
    • 也可以不这么极端,可以设置一个标准水位线,当阻塞队列中的数据量 < 水位线时,让生产者生产数据,> 水位线时,让消费者消费数据。
    • 这里就实现 空 / 满 这种极端做法实现。
  • 注:由生产者线程通知消费者线程消费,由消费者线程通知生产者线程生产。
    • 当消费者线程发现阻塞队列空了之后,就要通知生产者线程生产数据,然后消费者去指定条件变量处等待。
    • 当生产者线程发现阻塞队列满了之后,就要通知消费者线程消费数据,然后生产者去指定条件变量处等待。
#include <queue>
#include <iostream>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>using std::cout;
using std::endl;
using std::queue;const int num = 6;          // 设阻塞队列的容量为 6template<typename T>
class block_queue
{
private:queue<T> q;           // 用队列实现阻塞队列, 阻塞队列属于临界资源int cap;                // 阻塞队列的容量pthread_mutex_t lock;   // 互斥锁, 用于保护使用阻塞队列的临界区pthread_cond_t full;    // 生产者线程用的条件变量, 当阻塞队列满时去这里等待pthread_cond_t empty;   // 消费者线程用的条件变量, 当阻塞队列空时去这里等待private:// 上锁void lock_queue(){pthread_mutex_lock(&lock);}// 解锁void unlock_queue(){pthread_mutex_unlock(&lock);}// 让生产者线程去 full 条件变量处等待void product_wait(){pthread_cond_wait(&full, &lock);}// 让消费者线程去 empty 条件变量处等待void consume_wait(){pthread_cond_wait(&empty, &lock);}// 唤醒在 full 条件变量队列等待的的队头的生产者线程void notify_product(){pthread_cond_signal(&full);}// 唤醒在 empty 条件变量队列等待的的队头的消费者线程void notify_consume(){pthread_cond_signal(&empty);}// 判断当前阻塞队列是否为空bool is_empty(){return 0 == q.size();}// 判断当前阻塞队列是否为满bool is_full(){return q.size() == cap;}public:// 构造函数block_queue(int _cap = num) : cap(_cap){pthread_mutex_init(&lock, nullptr);	// 初始化互斥锁pthread_cond_init(&full, nullptr);	// 初始化生产者线程用的条件变量pthread_cond_init(&empty, nullptr);	// 初始化消费者线程用的条件变量}// 生产者线程往阻塞队列中放数据void push_data(const T &data){lock_queue();		 	// 下面的代码要访问临界资源 (阻塞队列) 了, 是临界区, 上锁while (is_full())       // 阻塞队列满了{notify_consume();   // 通知消费者线程消费数据cout << "阻塞队列满了, 通知消费者消费数据, 让生产者停止生产" << endl;product_wait();     // 让生产者线程等待,不要再生产了}q.push(data);           // 阻塞队列没满时,生产者线程会一直往里面放数据unlock_queue();			// 访问完临界区了, 解锁}// 消费者线程从阻塞队列中拿数据void pop_data(T *data){lock_queue();			// 下面的代码要访问临界资源 (阻塞队列) 了, 是临界区, 上锁while (is_empty())  	// 阻塞队列空了{notify_product();	// 通知生产者线程生产数据cout << "阻塞队列空了, 通知生产者生产数据, 让消费者停止消费" << endl;consume_wait();		// 让消费者线程等待,不要再拿取数据了}*data = q.front();		// 阻塞队列没空时,消费者线程会从里面拿取数据q.pop();unlock_queue();			// 访问完临界区了, 解锁}// 析构函数~block_queue(){pthread_mutex_destroy(&lock);   // 销毁保护阻塞队列的互斥锁pthread_cond_destroy(&full);    // 销毁生产者线程用的条件变量pthread_cond_destroy(&empty);   // 销毁消费者线程用的条件变量}
};// 消费者线程调用的函数
void *consumer(void *arg)
{int data;block_queue<int> *bqp = (block_queue<int> *)arg;while (true){bqp->pop_data(&data);    // 消费者线程用 data 从阻塞队列获取数据cout << "消费者拿取数据完毕, 拿取的数据是: " << data << endl;sleep(1);}
}// 生产者线程调用的函数
void *producter(void *arg)
{// 生产者线程生产的数据是一些随机数block_queue<int> *bqp = (block_queue<int> *)arg;srand((unsigned long)time(NULL));       // 随机数种子while (true){int data = rand() % 1024;           // 随机数bqp->push_data(data);               // 将生产者生产的随机数放入阻塞队列cout << "生产者生产数据完毕, 生产的数据是: " << data << endl;sleep(1);}
}int main()
{block_queue<int> bq; 	// 阻塞队列对象pthread_t cid;  		// 消费者线程的 idpthread_t pid;  		// 生产者线程的 id// 创建生产者和消费者线程, 让它们去执行各自的函数pthread_create(&cid, nullptr, consumer, (void *)&bq);    // 将阻塞队列对象作为参数传递给消费者线程调用的函数pthread_create(&pid, nullptr, producter, (void *)&bq);   // 将阻塞队列对象作为参数传递给生产者线程调用的函数pthread_join(cid, nullptr); // 主线程等待消费者线程pthread_join(pid, nullptr); // 主线程等待生产者线程return 0;
}

🌈 三、POSIX 信号量

⭐ 1. POSIX 信号量概念

  • POSIX 信号量的作用和 SystemV 信号量相同,都是用于同步操作,从而达到无冲突的访问资源的目的;但 POSIX 信号量可以用于线程间同步

🌙 1.1 信号量的本质

  • POSIX 信号量本质上是一个计数器,用于描述临界资源数量的计数器,能够更细粒度的对临界资源进行管理。

    • 假设当前有一个可以容纳 100 个人的博物馆,信号量的初始值就是 100,表示博物馆可提供的资源数。
    • 每当进去一个人时,信号量的值就减 1;每当出去一个人时,信号量的值就加 1。
    • 当信号量的值为 0 时,说明博物馆已经装不下更多人了,让后面的游客 (线程) 一直等到信号量不为 0。
  • 执行流在进入临界区之前,都应该申请信号量,申请成功后才具备操作临界资源的权限,当操作完毕后也应该释放信号量。

image-20240921104451355

🌙 1.2 信号量的 PV 操作

  • P 操作:将申请信号量称为 P 操作,申请信号量本质就是申请对临界资源的使用权限,当申请成功时会让信号量的值 - 1。因此 P 操作的本质就是让信号量这个计数器的值 - 1。
  • V 操作:将释放信号量成为 V 操作,释放信号量本质就是归还对临界资源的使用权限,当释放成功时会让信号量的值 + 1。因此 V 操作的本质就是让信号量这个计数器的值 + 1。

信号量的 PV 操作必须是原子的

  • 和争锁一样,多执行流之间为了访问临界资源也会竞争信号量,因此信号量也会被多执行流同时访问,即信号量本身就是个临界资源。
  • 但信号量本质上是为了保护临界资源,不可能说再弄个信号量或锁去保护信号量,因此信号量的 PV 操作必须是原子的。

⭐ 2. POSIX 信号量函数

  • 信号量的数据类型是 sem_t,可以使用该类型定义信号量。
  • 信号量函数的返回值:调用函数成功时返回 0,失败时返回 - 1。
  • 在使用信号量函数之前,应该先使用 #include <semaphore.h> 引入库文件。

🌙 2.1 初始化信号量

#include <semaphore.h>int sem_init(sem_t *sem, 		/* sem 表示需要初始化的信号量 */int pshared, 		/* 设置 sem 的共享方式,为 0 在线程间共享,非 0 在进程间共享 */unsigned int value); /* 信号量 (计数器) 的初始值 */

🌙 2.2 销毁信号量

#include <semaphore.h>int sem_destroy(sem_t *sem);	// sem 表示要销毁的信号量

🌙 2.3 申请信号量 (等待信号量)

  • 申请信号量 (等待信号量) 就是 PV 操作中的 P 操作。
    • 注:申请信号量的 P 操作应该在线程争锁之前进行,只有在确定有资源的情况下才让线程去争锁。
#include <semaphore.h>int sem_wait(sem_t *sem);	// sem 表示线程需要申请的信号量
  • 调用该函数时,如果申请信号量成功,则让信号量的值 - 1;
  • 如果申请信号量失败,则将调用该函数的线程在 sem 信号量的等待队列处挂起等待。

🌙 2.4 释放信号量 (发布信号量)

  • 释放信号量 (发布信号量) 就是 PV 操作中的 V 操作。
    • 注:释放信号量的 V 操作应该在线程解锁之后进行。
#include <semaphore.h>int sem_post(sem_t *sem);	// sem 表示线程需要释放的信号量
  • 调用该函数时,如果释放信号量成功,则让信号量的值 + 1。

🌈 四、基于环形队列的生产消费模型

⭐ 1. 环形队列的概念

  • 环形队列与阻塞队列最大的不同就是,环形队列能够构成一个环。
  • 在环形队列中,生产者和消费者一开始可以指向同一个位置,启动之后,让生产者先生产数据,消费者跟在后面消费数据。

image-20240921141906901

🌙 1.1 空间资源和数据资源

  • 并不一定说,只有数据才是资源,角色的不同,也会导致对资源的认识不同。

  • 对于生产者来说,它关心的是缓冲区的空间资源,而对于消费者来说,它关心的是缓冲区中的数据资源

  • 缓冲区中只要有空间,生产者就能干活;同理,缓冲区中只要有数据,消费者就能干活。

⭐ 2. 环形队列的规则

🌙 2.1 生产者和消费者不能访问同一个位置

  • 如果生产者和消费者访问的是环形队列中相同的位置,就可能会出现数据不一致的问题。
  • 消费者想拿旧数据,生产者新生产出的数据可能会将该位置旧有的数据给覆盖掉,但消费者又不知道自己拿到的是新的数据。

image-20240921143003333

🌙 2.2 生产者不能快消费者一圈以上

  • 如果生产者线程跑的太快了,绕一圈回来撞上在后面拿数据的消费者线程,如果生产者此时还不停下来,就会覆盖掉之前的数据。
  • 这条规则本质上就是生产者和消费者不能访问同一个位置。

image-20240921161121392

🌙 2.3 消费者不能超过生产者

  • 消费者已经将环形队列中的数据消费完了,此时消费者已经追上了生产者。
  • 如果消费者想超过生产者继续往前走,前面就不会有数据可供消费者消费了。
  • 就算前面有数据,消费者拿到的也是之前用过的旧数据,并不是所需的新数据。

image-20240921192649973

⭐ 3. 生产者消费者并发访问环形队列的场景

1. 以下情况会导致生产者和消费者访问同一个位置,不能实现并发访问

  1. 环形队列为空:消费者消费了一圈追上了生产者,生产者和消费者访问的是同一个位置,此时消费者应该停止消费让生产者去生产

  2. 环形队列为满:生产者生产力一圈追上了消费者,生产者和消费者访问的是同一个位置,此时生产者应该停止生产让消费者去消费

2. 生产者和消费者不访问同一位置时,可以实现并发访问

  • 生产者在前面跑,消费者在后面追,两者之间的距离小于一圈。
  • 这样可以让生产者一直能够获取空间资源,让消费者一直能够获取数据资源。

⭐ 4. 模拟实现基于环形队列的生产消费模型

  • 想要判断环形队列是否为 空 / 满,可以通过计数器。也可以预留一个位置作为满的状态。
  • 在了解了信号量之后,就可以使用信号量作为环形队列中资源数量的计数器
    • 可以定义两个信号量,分别是给生产者用的空间资源信号量,以及给消费者用的数据资源信号量
  • 当前要实现的是让生产者生产者先生产一个随机数,然后让消费者消费这个随机数,生产者与消费者之间就差一个身位。
#include <vector>
#include <iostream>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>using std::cout;
using std::endl;
using std::vector;const int default_cap = 4;              // 环形队列的默认大小// 定义并初始化一个生产者之间的互斥锁
pthread_mutex_t p_mutex = PTHREAD_MUTEX_INITIALIZER;// 定义并初始化一个消费者之间的互斥锁
pthread_mutex_t c_mutex = PTHREAD_MUTEX_INITIALIZER;template<typename T>
class ring_queue
{
private:vector<T> q;                        // 用数组来作为表示环形队列的容器int cap;                            // 环形队列的基本容量sem_t data_sem;                     // 给消费者使用的数据资源信号量sem_t space_sem;                    // 给生产者使用的空间资源信号量int consumer_step;                  // 记录消费者要往环形队列中 拿 数据的位置int producer_step;                  // 记录生产者要从环形队列中 放 数据的位置public:ring_queue(int _cap = default_cap) : q(_cap), cap(_cap){sem_init(&data_sem, 0, 0);      // 初始化数据信号量,数据信号量的初始值为 0sem_init(&space_sem, 0, cap);   // 初始化空间信号量,空间信号量的初始值为环形队列的大小consumer_step = 0;              // 消费者线程最开始从环形队列中 拿 数据的位置producer_step = 0;              // 生产者线程最开始往环形队列中 放 数据的位置}// 生产者线程往环形队列中添加数据void put_data(const T &data){sem_wait(&space_sem);           // P 操作: 少了一个空间资源,空间信号量的值 - 1pthread_mutex_lock(&p_mutex);   // 上锁, 所有申请到信号量的线程中,只允许一个去添加数据q[consumer_step] = data;        // 往环形队列中添加数据consumer_step++;                // 走到下一个要放数据的位置consumer_step %= cap;           // 防止越界pthread_mutex_unlock(&p_mutex); // 解锁sem_post(&data_sem);            // V 操作: 多个一个数据资源,数据信号量的值 + 1}// 消费者线程从环形队列中获取数据void get_data(T* data){sem_wait(&data_sem);            // P 操作: 少了一个数据资源,数据信号量的值 - 1pthread_mutex_lock(&c_mutex);   // 上锁, 所有申请到信号量的线程中,只允许一个去获取数据*data = q[producer_step];       // 从环形队列中拿取数据producer_step++;                // 走到下一个要拿数据的位置producer_step %= cap;           // 防止越界pthread_mutex_unlock(&c_mutex); // 解锁sem_post(&space_sem);           // V 操作: 多了一个空间资源,空间信号量的值 + 1}~ring_queue(){sem_destroy(&data_sem);         // 销毁消费者使用的数据信号量sem_destroy(&space_sem);        // 销毁生产者使用的空间信号量}
};// 消费者线程调用的函数
void *consumer(void *arg)
{int data;ring_queue<int> *rqp = (ring_queue<int> *)arg;while (true){rqp->get_data(&data);   // 从环形队列中获取数据// 显示器也是临界资源,也需要用锁保护起来pthread_mutex_lock(&p_mutex);cout << "consumer get data: " << data << endl;pthread_mutex_unlock(&p_mutex);sleep(1);}
}// 生产者线程调用的函数
void *producer(void *arg)
{ring_queue<int> *rqp = (ring_queue<int> *)arg;srand((unsigned long)time(nullptr));while (true){int data = rand() % 1024;rqp->put_data(data);    // 往环形队列中添加数据// 显示器也是临界资源,也需要用锁保护起来pthread_mutex_lock(&c_mutex);cout << "producer put data: " << data << endl;pthread_mutex_unlock(&c_mutex);sleep(1);}
}// 主线程
int main()
{ring_queue<int> rq; // 定义环形队列对象pthread_t cid;      // 记录消费者线程的 IDpthread_t pid;      // 记录生产者线程的 ID// 创建生产者和消费者线程,并让两种线程调用指定函数pthread_create(&cid, nullptr, consumer, (void *)&rq);   // 创建消费者线程pthread_create(&pid, nullptr, producer, (void *)&rq);   // 创建生产者线程pthread_join(cid, nullptr); // 主线程等待消费者线程退出pthread_join(pid, nullptr); // 主线程等待生产者线程退出return 0;
}

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

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

相关文章

ASP.NET Core 创建使用异步队列

示例图 在 ASP.NET Core 应用程序中&#xff0c;执行耗时任务而不阻塞线程的一种有效方法是使用异步队列。在本文中&#xff0c;我们将探讨如何使用 .NET Core 和 C# 创建队列结构以及如何使用此队列异步执行操作。 步骤 1&#xff1a;创建 EmailMessage 类 首先&#xff0c…

【零基础入门产品经理】学习准备篇 | 需要学一些什么呢?

前言&#xff1a; 零实习转行产品经理经验分享01-学习准备篇_哔哩哔哩_bilibili 该篇内容主要是对bilibili这个视频的观后笔记~谢谢美丽滴up主友情分享。 全文摘要&#xff1a;如何在0实习且没有任何产品相关经验下&#xff0c;如何上岸产品经理~ 目录 一、想清楚为什么…

AIGC教程:如何用Stable Diffusion+ControlNet做角色设计?

前言 对于生成型AI的画图能力&#xff0c;尤其是AI画美女的能力&#xff0c;相信同行们已经有了充分的了解。然而&#xff0c;对于游戏开发者而言&#xff0c;仅仅是漂亮的二维图片实际上很难直接用于角色设计&#xff0c;因为&#xff0c;除了设计风格之外&#xff0c;角色设…

C#知识|基于反射和接口实现抽象工厂设计模式

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 01 应用场景 在项目的多数据库支持上、业务的多算法封装、以及各种变化的业务中&#xff1b; 02 抽象工厂组成 抽象工厂包括抽象产品&#xff08;即业务接口&#xff0c;可以通过抽象类或抽象接口设计&#xff09;…

mfc140u.dll缺失?快速解决方法全解析,解决mfc140u.dll错误

当你的电脑出现找不到mfc140u.dll的问题&#xff0c;不少用户在使用电脑时陷入了困扰。这个错误提示就像一道屏障&#xff0c;阻挡了用户正常使用某些软件。无论是办公软件、游戏还是专业的设计工具&#xff0c;一旦出现这个问题&#xff0c;都会导致软件无法正常运行。如果您也…

mips指令系统简介

**MIPS&#xff08;Microprocessor without Interlocked Piped Stages&#xff09;**&#xff1a;这是一种RISC&#xff08;精简指令集计算&#xff09;芯片架构&#xff0c;由John L. Hennessy设计&#xff0c;特点是没有内部互锁的流水级&#xff0c;简化了处理器设计。 对比…

【WRF工具】cmip6-to-wrfinterm工具概述:生成WRF中间文件

cmip6-to-wrfinterm工具概述 cmip6-to-wrfinterm工具安装cmip6-to-wrfinterm工具使用快速启动&#xff08;Quick start&#xff09;情景1&#xff1a;MPI-ESM-1-2-HR&#xff08;默认&#xff09;&#xff1a;情景2&#xff1a;BCMM情景3&#xff1a;EC-Earth3 更改使用&#x…

【三步 完全离线搭建 openwebui 】

完全离线linux 版open webui 的搭建 1.在具有网络连接的环境中下载whl 在有网络的环境&#xff0c;使用pip download可以保存所有的依赖包,可以使用-i 指定清华的镜像源加速下载速度。 # 命令&#xff1a; pip download <package_name> --only-binary:all: --wheel --…

使用微服务Spring Cloud集成Kafka实现异步通信

在微服务架构中&#xff0c;使用Spring Cloud集成Apache Kafka来实现异步通信是一种常见且高效的做法。Kafka作为一个分布式流处理平台&#xff0c;能够处理高吞吐量的数据&#xff0c;非常适合用于微服务之间的消息传递。 微服务之间的通信方式包括同步通信和异步通信。 1&a…

ansible之playbook\shell\script模块远程自动安装nginx

通过shell模块&#xff0c; 编写安装nginx脚本&#xff0c;为yaml脚本&#xff0c;远程到135机器上安装并启动nginx - hosts: 192.168.45.135remote_user: roottasks:- name: 安装Nginx依赖环境和库文件yum: namewget,tar,make,gcc,pcre-devel,pcre,zlib-devel stateinstalle…

tr命令:替换文本中的字符

一、命令简介 ​tr​ 命令用于转换或删除文件中的字符。它可以从标准输入中读取数据&#xff0c;对数据进行字符替换、删除或压缩&#xff0c;并将结果输出到标准输出。 ‍ 二、命令参数 格式 tr [选项] [集合1] [集合2]选项和参数 ​ ​-c​​: 指定 集合 1 的补集。​ …

【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【下篇】

【STM32开发笔记】移植AI框架TensorFlow到STM32单片机【下篇】 一、上篇回顾二、项目准备2.1 准备模板项目2.2 支持计时功能2.3 配置UART4引脚2.4 支持printf重定向到UART42.5 支持printf输出浮点数2.6 支持printf不带\r的换行2.7 支持ccache编译缓存 三、TFLM集成3.1 添加tfli…

“卷”智能, 从高质量算力开始

算力即国力&#xff0c;这已是产业共识。 当人工智能浪潮席卷全球之际&#xff0c;大家深刻感受到发展算力产业的重要性和紧迫性&#xff0c;高质量的人工智能算力已经与国家竞争、产业升级和企业转型息息相关。 去年&#xff0c;《算力基础设施高质量发展行动计划》的颁布&a…

springboot整合MybatisPlus+MySQL

上一篇&#xff1a;springboot整合sentinel和对feign熔断降级 文章目录 一、准备二、主要工作三、具体步骤3.1 准备数据库环境3.20 pre引入依赖3.2 引入依赖3.3 bootstrap.yml配置mybatisplus3.40 pre引入service、mapper3.4 引入实体类、service、mapper 四、测试目录结构 五…

InnoDB 死锁

文章目录 死锁案例等待超时时间InnoDB 状态信息死锁日志 死锁检测死锁日志分析 死锁是指多个事务无法继续进行的情况&#xff0c;因为每个事务都持有另一个事务所需的锁。因为所有涉及的事务都在等待同一资源可用&#xff0c;所以它们都不会释放它所持有的锁。 当事务锁定多个…

MongoDB 工具包安装(mongodb-database-tools)

首先到官网下载工具包&#xff0c;进入下面页面&#xff0c;复制连接地址&#xff0c;使用wget下载 cd /usr/local/mongodb5.0.14/wget https://fastdl.mongodb.org/tools/db/mongodb-database-tools-rhel70-x86_64-100.6.1.tgz 安装 tar -zxvf mongodb-database-tools-rhel70-…

Python库matplotlib之五

Python库matplotlib之五 小部件(widget)RangeSlider构造器APIs应用实列 TextBox构造器APIs应用实列 小部件(widget) 小部件(widget)可与任何GUI后端一起工作。所有这些小部件都要求预定义一个Axes实例&#xff0c;并将其作为第一个参数传递。 Matplotlib不会试图布局这些小部件…

LeetCode 热题 100 回顾2

干货分享&#xff0c;感谢您的阅读&#xff01;原文见&#xff1a;LeetCode 热题 100 回顾_力code热题100-CSDN博客 一、哈希部分 1.两数之和 &#xff08;简单&#xff09; 题目描述 给定一个整数数组 nums 和一个整数目标值 target&#xff0c;请你在该数组中找出 和为目标…

自制CANTool_DBC_Layout仿制_布局读取Signal(三)

1、读取DBC中解析格式空格问题报错解决方法 原来解析方式&#xff1a;BO_ 258 EPS_CANFD_StrWhlASts: 8 Test 有的DBC中数据格式&#xff1a;BO_ 80 GW_50: 8 GW &#xff08;多了一个空格&#xff09; 解析匹配规则修订为&#xff1a; string MessageRegex "BO…

手机改IP地址怎么弄?全面解析与操作指南

在当今数字化时代&#xff0c;IP地址作为设备在网络中的唯一标识&#xff0c;其重要性不言而喻。有时候&#xff0c;出于隐私保护、网络访问需求或其他特定原因&#xff0c;我们可能需要更改手机的IP地址。然而&#xff0c;对于大多数普通用户来说&#xff0c;如何操作可能还是…