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

【Linux】29.Linux 多线程(3)

文章目录

    • 8.4 生产者消费者模型
      • 8.4.1 为何要使用生产者消费者模型
      • 8.4.2 生产者消费者模型优点
    • 8.5 基于BlockingQueue的生产者消费者模型
      • 8.5.1 C++ queue模拟阻塞队列的生产消费模型
    • 8.6. 为什么pthread_cond_wait 需要互斥量?
    • 8.7 条件变量使用规范
    • 8.8 条件变量的封装
    • 8.9 POSIX信号量
      • 8.9.1 基于环形队列的生产消费模型
        • 实现一个信号量的封装
        • 实现一个线程安全的环形队列


8.4 生产者消费者模型

8.4.1 为何要使用生产者消费者模型

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


8.4.2 生产者消费者模型优点

  • 解耦
  • 支持并发
  • 支持忙闲不均

62ea71f2aec93f2e52ffb13d09048f15


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

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

04ac912c03dc54bfd7e721a52b877e29


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

单生产者,单消费者

代码:

#ifndef __BLOCK_QUEUE_HPP__  // 头文件防重包含宏
#define __BLOCK_QUEUE_HPP__#include <iostream>
#include <queue>
#include <pthread.h>  // POSIX线程库头文件// 阻塞队列类模板,实现单生产者-单消费者模型
template <typename T>
class BlockQueue
{
private:// 检查队列是否已满bool IsFull(){return _block_queue.size() == _cap;}// 检查队列是否为空bool IsEmpty(){return _block_queue.empty();}public:// 构造函数:初始化队列容量和同步原语BlockQueue(int cap) : _cap(cap){// 单生产者-单消费者模型不需要记录等待数量pthread_mutex_init(&_mutex, nullptr);          // 初始化互斥锁pthread_cond_init(&_product_cond, nullptr);    // 初始化生产者条件变量pthread_cond_init(&_consum_cond, nullptr);     // 初始化消费者条件变量}// 生产者接口 - 仅供单个生产者线程调用// 参数in: 要入队的元素void Push(const T& in){pthread_mutex_lock(&_mutex);    // 获取互斥锁,进入临界区// 如果队列满,生产者需要等待// 单生产者场景使用if而不是while,因为不会有虚假唤醒if(IsFull()){// pthread_cond_wait会:// 1. 释放互斥锁// 2. 阻塞等待条件变量// 3. 被唤醒后重新获取互斥锁pthread_cond_wait(&_product_cond, &_mutex);}// 将数据放入队列_block_queue.push(in);// 唤醒可能在等待的消费者// 单消费者场景下最多只有一个线程在等待pthread_cond_signal(&_consum_cond);pthread_mutex_unlock(&_mutex);  // 释放互斥锁,离开临界区}// 消费者接口 - 仅供单个消费者线程调用// 参数out: 用于存储出队元素的指针void Pop(T* out){pthread_mutex_lock(&_mutex);    // 获取互斥锁,进入临界区// 如果队列空,消费者需要等待// 单消费者场景使用if而不是while,因为不会有虚假唤醒if(IsEmpty()){pthread_cond_wait(&_consum_cond, &_mutex);}// 从队列取出数据*out = _block_queue.front();    // 获取队首元素_block_queue.pop();             // 移除队首元素// 唤醒可能在等待的生产者// 单生产者场景下最多只有一个线程在等待pthread_cond_signal(&_product_cond);pthread_mutex_unlock(&_mutex);   // 释放互斥锁,离开临界区}// 析构函数:清理同步原语~BlockQueue(){pthread_mutex_destroy(&_mutex);           // 销毁互斥锁pthread_cond_destroy(&_product_cond);     // 销毁生产者条件变量pthread_cond_destroy(&_consum_cond);      // 销毁消费者条件变量}private:std::queue<T> _block_queue;        // 底层队列容器int _cap;                          // 队列最大容量pthread_mutex_t _mutex;            // 互斥锁,保护共享资源pthread_cond_t _product_cond;      // 生产者条件变量,用于生产者等待pthread_cond_t _consum_cond;       // 消费者条件变量,用于消费者等待
};#endif

多生产者,多消费者

代码:

#ifndef __BLOCK_QUEUE_HPP__
#define __BLOCK_QUEUE_HPP__#include <iostream>
#include <string>
#include <queue>
#include <pthread.h>// 阻塞队列类模板,用于实现生产者-消费者模型
template <typename T>
class BlockQueue
{
private:// 检查队列是否已满bool IsFull(){return _block_queue.size() == _cap;}// 检查队列是否为空bool IsEmpty(){return _block_queue.empty();}public:// 构造函数:初始化队列容量和同步原语BlockQueue(int cap) : _cap(cap){_productor_wait_num = 0;  // 等待的生产者数量_consumer_wait_num = 0;   // 等待的消费者数量pthread_mutex_init(&_mutex, nullptr);           // 初始化互斥锁pthread_cond_init(&_product_cond, nullptr);     // 初始化生产者条件变量pthread_cond_init(&_consum_cond, nullptr);      // 初始化消费者条件变量}// 入队方法:生产者接口void Enqueue(T &in){pthread_mutex_lock(&_mutex);  // 获取互斥锁,进入临界区// 当队列满时,生产者需要等待while(IsFull())  // 使用while而不是if,防止虚假唤醒{// 生产者等待流程:// 1. 增加等待计数// 2. 释放互斥锁并等待条件变量// 3. 被唤醒后减少等待计数_productor_wait_num++;pthread_cond_wait(&_product_cond, &_mutex);_productor_wait_num--;}// 将数据放入队列_block_queue.push(in);// 如果有消费者在等待,唤醒其中一个if(_consumer_wait_num > 0)pthread_cond_signal(&_consum_cond);  // 也可以用broadcast唤醒所有pthread_mutex_unlock(&_mutex);  // 释放互斥锁}// 出队方法:消费者接口void Pop(T *out){pthread_mutex_lock(&_mutex);  // 获取互斥锁,进入临界区// 当队列空时,消费者需要等待while(IsEmpty())  // 使用while防止虚假唤醒{// 消费者等待流程:// 1. 增加等待计数// 2. 释放互斥锁并等待条件变量// 3. 被唤醒后减少等待计数_consumer_wait_num++;pthread_cond_wait(&_consum_cond, &_mutex);_consumer_wait_num--;}// 从队列取出数据*out = _block_queue.front();_block_queue.pop();// 如果有生产者在等待,唤醒其中一个if(_productor_wait_num > 0)pthread_cond_signal(&_product_cond);pthread_mutex_unlock(&_mutex);  // 释放互斥锁}// 析构函数:清理同步原语~BlockQueue(){pthread_mutex_destroy(&_mutex);pthread_cond_destroy(&_product_cond);pthread_cond_destroy(&_consum_cond);}private:std::queue<T> _block_queue;        // 底层队列容器int _cap;                          // 队列容量pthread_mutex_t _mutex;            // 互斥锁,保护共享资源pthread_cond_t _product_cond;      // 生产者条件变量pthread_cond_t _consum_cond;       // 消费者条件变量int _productor_wait_num;           // 等待的生产者数量int _consumer_wait_num;            // 等待的消费者数量
};#endif

这里采用模版,是想告诉我们,队列中不仅仅可以放置内置类型,比如int, 对象也可以作为任务来参与生产消费的过程。


8.6. 为什么pthread_cond_wait 需要互斥量?

  • 条件等待是线程间同步的一种手段,如果只有一个线程,条件不满足,一直等下去都不会满足,所以必须要有一个线程通过某些操作,改变共享变量,使原先不满足的条件变得满足,并且友好的通知等待在条件变量上的线程。
  • 条件不会无缘无故的突然变得满足了,必然会牵扯到共享数据的变化。所以一定要用互斥锁来保护。没有互斥锁就无法安全的获取和修改共享数据。

4c49e3814e454e94ca26513d160414ac

按照上面的说法,我们设计出如下的代码:先上锁,发现条件不满足,解锁,然后等待在条件变量上不就行了?如下代码:

// 错误的设计
pthread_mutex_lock(&mutex);
while (condition_is_false) {pthread_mutex_unlock(&mutex);//解锁之后,等待之前,条件可能已经满足,信号已经发出,但是该信号可能被错过pthread_cond_wait(&cond);pthread_mutex_lock(&mutex);
}
pthread_mutex_unlock(&mutex);
  • 由于解锁和等待不是原子操作。调用解锁之后, pthread_cond_wait 之前,如果已经有其他线程获取到互斥量,摒弃条件满足,发送了信号,那么 pthread_cond_wait 将错过这个信号,可能会导致线程永远阻塞在这个 pthread_cond_wait 。所以解锁和等待必须是一个原子操作。
  • int pthread_cond_wait(pthread_cond_ t *cond,pthread_mutex_ t * mutex); 进入该函数后,会去看条件量等于0不?等于,就把互斥量变成1,直到cond_ wait返回,把条件量改成1,把互斥量恢复成原样。

8.7 条件变量使用规范

等待条件代码

pthread_mutex_lock(&mutex);
while (条件为假)pthread_cond_wait(cond, mutex);
修改条件
pthread_mutex_unlock(&mutex);

给条件发送信号代码

pthread_mutex_lock(&mutex);
设置条件为真
pthread_cond_signal(cond);
pthread_mutex_unlock(&mutex);

8.8 条件变量的封装

#pragma once  // 防止头文件重复包含,比#ifndef更现代的方式#include <iostream>
#include <string>
#include <pthread.h>
#include "Lock.hpp"  // 包含互斥锁的封装类// 条件变量模块命名空间
namespace CondModule
{// 使用互斥锁模块的命名空间using namespace LockModule;// 条件变量封装类// 目的:将pthread_cond_t的C接口封装为C++类,实现RAII机制class Cond{public:// 构造函数:初始化条件变量Cond(){// pthread_cond_init返回0表示成功,非0表示失败int n = pthread_cond_init(&_cond, nullptr);  // 默认属性初始化(void)n; // 暂时忽略返回值,实际使用时应该添加错误处理和日志}// 等待条件变量// 参数:互斥锁的引用,必须在调用Wait前已经获得锁void Wait(Mutex &mutex){// pthread_cond_wait会自动:// 1. 释放互斥锁// 2. 等待条件// 3. 被唤醒后重新获取锁int n = pthread_cond_wait(&_cond, mutex.GetMutexOriginal());(void)n;  // 暂时忽略返回值,实际使用时应该添加错误处理}// 唤醒一个等待的线程void Notify(){// 如果有多个线程在等待,则随机唤醒其中一个int n = pthread_cond_signal(&_cond);(void)n;  // 暂时忽略返回值}// 唤醒所有等待的线程void NotifyAll(){// 唤醒所有等待该条件变量的线程// 被唤醒的线程仍需要重新获得互斥锁才能继续执行int n = pthread_cond_broadcast(&_cond);(void)n;  // 暂时忽略返回值}// 析构函数:销毁条件变量~Cond(){// 销毁条件变量,释放相关资源int n = pthread_cond_destroy(&_cond);(void)n; // 暂时忽略返回值,实际使用时应该添加错误处理和日志}private:pthread_cond_t _cond;  // 底层条件变量// 禁止复制和赋值// C++11前的方式:声明为private但不实现Cond(const Cond&);Cond& operator=(const Cond&);};
}

为了让条件变量更具有通用性,建议封装的时候,不要在Cond类内部引用对应的封装互斥量,要不然后面组合的时候,会因为代码耦合的问题难以初始化,因为一般而言Mutex和Cond基本是一起创建的。


8.9 POSIX信号量

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

信号量的本质是一把计数器,那么这把计数器的本质是什么?

是用来描述资源数目的,把资源是否就绪放在了临界区之外。申请信号量时,其实就间接的已经在做判断了。

初始化信号量

#include <semaphore.h>
int sem_init(sem_t *sem, int pshared, unsigned int value);
参数:pshared:0表示线程间共享,非零表示进程间共享value:信号量初始值

销毁信号量

int sem_destroy(sem_t *sem);

等待信号量

功能:等待信号量,会将信号量的值减1
int sem_wait(sem_t *sem); //P()

发布信号量

功能:发布信号量,表示资源使用完毕,可以归还资源了。将信号量值加1int sem_post(sem_t *sem);//V()

上一节生产者-消费者的例子是基于queue的,其空间可以动态分配,现在基于固定大小的环形队列重写这个程序(POSIX信号量):


8.9.1 基于环形队列的生产消费模型

环形队列采用数组模拟,用模运算来模拟环状特性

dd7272e89d88db62fda8f13f4583e89c

环形结构起始状态和结束状态都是一样的,不好判断为空或者为满,所以可以通过加计数器或者标记位来判断满或者空。另外也可以预留一个空的位置,作为满的状态。

ccb0345294ea86e1e81ef9b379abe8b2

但是我们现在有信号量这个计数器,就很简单的进行多线程间的同步过程。


实现一个信号量的封装
#pragma once  // 防止头文件重复包含#include <iostream>
#include <semaphore.h>  // POSIX信号量头文件// 信号量封装类
// 将POSIX信号量封装为C++类,实现RAII机制
// P-V操作源自荷兰语Proberen(尝试)/Verhogen(增加)
class Sem
{
public:// 构造函数:初始化信号量// 参数n: 信号量初始值,表示可用资源数量Sem(int n){// sem_init参数说明:// &_sem: 信号量对象// 0: 信号量类型,0表示线程间共享,非0表示进程间共享// n: 信号量初始值sem_init(&_sem, 0, n);}// P操作(等待操作)// 使信号量值减1,如果信号量值为0则阻塞// 对应生产者-消费者模型中的取走资源操作void P(){// sem_wait会导致:// 1. 信号量值大于0:将其减1并继续执行// 2. 信号量值等于0:阻塞等待,直到信号量大于0sem_wait(&_sem);}// V操作(释放操作)// 使信号量值加1,如果有线程阻塞则唤醒一个// 对应生产者-消费者模型中的放入资源操作void V(){// sem_post会:// 1. 将信号量值加1// 2. 如果有线程因为sem_wait阻塞,则唤醒其中一个sem_post(&_sem);}// 析构函数:销毁信号量~Sem(){// 释放信号量相关的资源sem_destroy(&_sem);}private:sem_t _sem;  // POSIX信号量对象// 禁止拷贝构造和赋值操作Sem(const Sem&) = delete;Sem& operator=(const Sem&) = delete;
};
  1. 核心功能

    • 实现了一个计数器,用于控制对共享资源的访问

    • 比如:有3个座位的餐厅,这个信号量初始值就设为3

  2. 主要操作

  • P() 操作(等待):
    • 想进入餐厅时调用
    • 如果还有座位(信号量>0),就能直接进入,计数器-1
    • 如果没座位(信号量=0),就要在门口等待
  • V() 操作(释放):
    • 离开餐厅时调用
    • 释放一个座位,计数器+1
    • 如果有人在等待,会唤醒一个等待的人

实现一个线程安全的环形队列
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <semaphore.h>
#include <pthread.h>// 环形队列实现 - 支持多生产者多消费者
// 设计思路 "321":
// 3: 需要处理三种同步关系
//    a) 生产者和消费者之间的互斥与同步
//    b) 多个生产者之间的互斥
//    c) 多个消费者之间的互斥
// 2: 使用两把互斥锁
//    - 一把用于生产者间互斥
//    - 一把用于消费者间互斥
// 1: 一个循环队列作为数据缓冲区template<typename T>
class RingQueue
{
private:// 辅助函数:加锁void Lock(pthread_mutex_t &mutex){pthread_mutex_lock(&mutex);}// 辅助函数:解锁void Unlock(pthread_mutex_t &mutex){pthread_mutex_unlock(&mutex);}public:// 构造函数:初始化环形队列和同步原语// 参数cap: 环形队列容量RingQueue(int cap): _ring_queue(cap),        // 初始化vector大小为cap_cap(cap),                 // 保存容量_room_sem(cap),            // 空闲位置信号量,初值为容量_data_sem(0),              // 数据项信号量,初值为0_productor_step(0),        // 生产位置,从0开始_consumer_step(0)          // 消费位置,从0开始{// 初始化生产者和消费者互斥锁pthread_mutex_init(&_productor_mutex, nullptr);pthread_mutex_init(&_consumer_mutex, nullptr);}// 入队方法 - 生产者调用// 参数in: 要入队的数据void Enqueue(const T &in){_room_sem.P();    // 等待空闲位置Lock(_productor_mutex);    // 生产者间互斥// 此时一定有空间可用(由信号量保证)_ring_queue[_productor_step++] = in;  // 放入数据_productor_step %= _cap;              // 循环更新位置Unlock(_productor_mutex);   // 解除生产者间互斥_data_sem.V();    // 通知有新数据可用}// 出队方法 - 消费者调用// 参数out: 存储出队数据的指针void Pop(T *out){_data_sem.P();    // 等待有数据可用Lock(_consumer_mutex);    // 消费者间互斥*out = _ring_queue[_consumer_step++];  // 取出数据_consumer_step %= _cap;                // 循环更新位置Unlock(_consumer_mutex);   // 解除消费者间互斥_room_sem.V();    // 通知有新空位可用}// 析构函数:清理同步原语~RingQueue(){pthread_mutex_destroy(&_productor_mutex);pthread_mutex_destroy(&_consumer_mutex);}private:// 1. 环形队列存储结构std::vector<T> _ring_queue;    // 存储数据的环形缓冲区int _cap;                      // 队列容量// 2. 生产和消费位置int _productor_step;           // 生产者放入位置int _consumer_step;            // 消费者取出位置// 3. 信号量 - 用于生产者消费者同步Sem _room_sem;                 // 空闲位置信号量,控制生产者Sem _data_sem;                 // 数据项信号量,控制消费者// 4. 互斥锁 - 用于多生产者/消费者间互斥pthread_mutex_t _productor_mutex;  // 生产者间互斥pthread_mutex_t _consumer_mutex;   // 消费者间互斥
};

用一个餐厅自助取餐区的例子来解释:

  1. 基本结构

    • 想象一个传送带上有固定数量(cap)的餐盘位置

    • _ring_queue就是这个传送带

    • _productor_step是厨师放餐盘的位置

    • _consumer_step是顾客取餐盘的位置

  2. 核心功能

  • Enqueue(): 厨师(生产者)放餐
    • 检查是否有空位置(_room_sem)
    • 确保只有一个厨师在放餐(_productor_mutex)
    • 放入餐品
    • 通知有新餐品可取(_data_sem)
  • Pop(): 顾客(消费者)取餐
    • 检查是否有餐品可取(_data_sem)
    • 确保只有一个顾客在取餐(_consumer_mutex)
    • 取走餐品
    • 通知有新空位可用(_room_sem)
  1. 同步机制

    • 使用两个信号量控制生产和消费

    • 使用两个互斥锁确保多个生产者/消费者之间不冲突

  2. 环形特性

    • 位置到达末尾后回到开头继续使用(像传送带一样循环)

相关文章:

【Linux】29.Linux 多线程(3)

文章目录 8.4 生产者消费者模型8.4.1 为何要使用生产者消费者模型8.4.2 生产者消费者模型优点 8.5 基于BlockingQueue的生产者消费者模型8.5.1 C queue模拟阻塞队列的生产消费模型 8.6. 为什么pthread_cond_wait 需要互斥量?8.7 条件变量使用规范8.8 条件变量的封装8.9 POSIX信…...

利用UNIAPP实现短视频上下滑动播放功能

在 UniApp 中实现一个短视频上下滑动播放的功能,可以使用 swiper 组件来实现滑动效果,并结合 video 组件来播放短视频。以下是一个完整的示例,展示如何在 UniApp 中实现这一功能。 1. 创建 UniApp 项目 如果你还没有创建 UniApp 项目,可以使用 HBuilderX 创建一个新的项目…...

vscode+CMake+Debug实现 及权限不足等诸多问题汇总

环境说明 有空再补充 直接贴两个json tasks.json {"version": "2.0.0","tasks": [{"label": "cmake","type": "shell","command": "cmake","args": ["../"…...

【提示词工程】探索大语言模型的参数设置:优化提示词交互的技巧

在与大语言模型(Large Language Model, LLM)进行交互时,提示词的设计和参数设置直接影响生成内容的质量和效果。无论是通过 API 调用还是直接使用模型,掌握模型的参数配置方法都至关重要。本文将为您详细解析常见的参数设置及其应用场景,帮助您更高效地利用大语言模型。 …...

基于 .NET 8.0 gRPC通讯架构设计讲解,客户端+服务端

目录 1.简要说明 2.服务端设计 2.1 服务端创建 2.2 服务端设计 2.3 服务端业务模块 3.客户端设计-控制台 4.客户端设计-Avalonia桌面程序 5.客户端设计-MAUI安卓端程序 1.简要说明 gRPC 一开始由 google 开发&#xff0c;是一款语言中立、平台中立、开源的远程过程调用…...

6.Centos7上部署flask+SQLAlchemy+python+达梦数据库

情况说明 前面已经介绍了window上使用pycharm工具开发项目时,window版的python连接达梦数据库需要的第三方包。 这篇文章讲述,centos7上的python版本连接达梦数据库需要的第三方包。 之前是在windows上安装达梦数据库的客户端,将驱动包安装到windows版本的python中。(开…...

【C语言系列】深入理解指针(5)

深入理解指针&#xff08;5&#xff09; 一、sizeof和strlen的对比1.1sizeof1.2strlen1.3sizeof和strlen的对比 二、数组和指针笔试题解析2.1 一维数组2.2 字符数组2.2.1代码1&#xff1a;2.2.2代码2&#xff1a;2.2.3代码3&#xff1a;2.2.4代码4&#xff1a;2.2.5代码5&#…...

mysql自连接 处理层次结构数据

MySQL 的自连接&#xff08;Self Join&#xff09;是一种特殊的连接方式&#xff0c;它允许一个表与自身进行连接。自连接通常用于处理具有层次结构或递归关系的数据&#xff0c;或者当同一张表中的数据需要相互关联时。以下是几种常见的场景&#xff0c;说明何时应该使用自连接…...

##__VA_ARGS__有什么作用

##__VA_ARGS__ 是 C/C 中宏定义&#xff08;Macro&#xff09;的一种特殊用法&#xff0c;主要用于可变参数宏&#xff08;Variadic Macros&#xff09;的场景&#xff0c;解决当可变参数为空时可能导致的语法错误问题。以下是详细解释&#xff1a; 核心作用 消除空参数时的多余…...

鸿蒙 router.back()返回不到上个页面

1. 检查页面栈&#xff08;Page Stack&#xff09; 鸿蒙的路由基于页面栈管理&#xff0c;确保上一个页面存在且未被销毁。 使用 router.getLength() 检查当前页面栈长度&#xff1a; console.log(当前页面栈长度: ${router.getLength()}); 如果结果为 1&#xff0c;说明没有上…...

深度学习模型蒸馏技术的发展与应用

随着人工智能技术的快速发展&#xff0c;大型语言模型和深度学习模型在各个领域展现出惊人的能力。然而&#xff0c;这些模型的规模和复杂度也带来了显著的部署挑战。模型蒸馏技术作为一种优化解决方案&#xff0c;正在成为连接学术研究和产业应用的重要桥梁。本文将深入探讨模…...

STM32G0B1 ADC DMA normal

目标 ADC 5个通道&#xff0c;希望每1秒采集一遍&#xff1b; CUBEMX 配置 添加代码 #define ADC1_CHANNEL_CNT 5 //采样通道数 #define ADC1_CHANNEL_FRE 3 //单个通道采样次数&#xff0c;用来取平均值 uint16_t adc1_val_buf[ADC1_CHANNEL_CNT*ADC1_CHANNEL_FRE]; //传递…...

<tauri><rust><GUI>基于rust和tauri,在已有的前端框架上手动集成tauri示例

前言 本文是基于rust和tauri&#xff0c;由于tauri是前、后端结合的GUI框架&#xff0c;既可以直接生成包含前端代码的文件&#xff0c;也可以在已有的前端项目上集成tauri框架&#xff0c;将前端页面化为桌面GUI。 环境配置 系统&#xff1a;windows 10 平台&#xff1a;visu…...

模型 冗余系统(系统科学)

系列文章分享模型&#xff0c;了解更多&#x1f449; 模型_思维模型目录。为防故障、保运行的备份机制。 1 冗余系统的应用 1.1 冗余系统在企业管理中的应用-金融行业信息安全的二倍冗余技术 在金融行业&#xff0c;信息安全是保障业务连续性和客户资产安全的关键。随着数字化…...

Deepseek部署的模型参数要求

DeepSeek 模型部署硬件要求 模型名称参数量显存需求&#xff08;推理&#xff09;显存需求&#xff08;微调&#xff09;CPU 配置内存要求硬盘空间适用场景DeepSeek-R1-1.5B1.5B4GB8GB最低 4 核&#xff08;推荐多核&#xff09;8GB3GB低资源设备部署&#xff0c;如树莓派、旧…...

AI-学习路线图-PyTorch-我是土堆

1 需求 PyTorch深度学习快速入门教程&#xff08;绝对通俗易懂&#xff01;&#xff09;【小土堆】_哔哩哔哩_bilibili PyTorch 深度学习快速入门教程 配套资源 链接 视频教程 https://www.bilibili.com/video/BV1hE411t7RN/ 文字教程 https://blog.csdn.net/xiaotudui…...

[LeetCode]day17 349.两个数组的交集

https://leetcode.cn/problems/intersection-of-two-arrays/description/ 题目描述 给定两个数组 nums1 和 nums2 &#xff0c;返回它们的交集。 输出结果中的每个元素一定是唯一的。 我们可以不考虑输出结果的顺序 。 示例 1&#xff1a; 输入&#xff1a;nums1 [1,2,2,1…...

axios 发起 post请求 json 需要传入数据格式

• 1. axios 发起 post请求 json 传入数据格式 • 2. axios get请求 1. axios 发起 post请求 json 传入数据格式 使用 axios 发起 POST 请求并以 JSON 格式传递数据是前端开发中常见的操作。 下面是一个简单的示例&#xff0c;展示如何使用 axios 向服务器发送包含 JSON 数…...

linux交叉编译paho-mqtt-c

下载源代码&#xff1a; https://github.com/eclipse-paho/paho.mqtt.c.git 编译&#xff1a; 如果mqtt不需要SSL安全认证&#xff0c;可以直接执行&#xff08;注意把编译工具链路径改成自己的&#xff09; cd paho.mqtt.c-1.3.13/ mkdir install # 创建安装目录 mkdir…...

feign Api接口中注解问题:not annotated with HTTP method type (ex. GET, POST)

Bug Description 在调用Feign api时&#xff0c;出现如下异常&#xff1a; java.lang.IllegalStateException: Method PayFeignSentinelApi#getPayByOrderNo(String) not annotated with HTTPReproduciton Steps 1.启动nacos-pay-provider服务&#xff0c;并启动nacos-pay-c…...

安装指定版本的pnpm

要安装指定版本的 pnpm&#xff0c;可以使用以下方法&#xff1a; 方法 1: 使用 pnpm 安装指定版本 你可以通过 pnpm 的 add 命令来安装指定版本&#xff1a; pnpm add -g pnpm<版本号>例如&#xff0c;安装 pnpm 的 7.0.0 版本&#xff1a; pnpm add -g pnpm7.0.0方法…...

【系统设计】Spring、SpringMVC 与 Spring Boot 技术选型指南:人群、场景与实战建议

在 Java 开发领域&#xff0c;Spring 生态的技术选型直接影响项目的开发效率、维护成本和长期扩展性。然而&#xff0c;面对 Spring、SpringMVC 和 Spring Boot 这三个紧密关联的框架&#xff0c;开发者常常陷入纠结&#xff1a;该从何入手&#xff1f;如何根据团队能力和业务需…...

常用数据结构之String字符串

字符串 在Java编程语言中&#xff0c;字符可以使用基本数据类型char来保存&#xff0c;在 Java 中字符串属于对象&#xff0c;Java 提供了 String 类来创建和操作字符串。 操作字符串常用的有三种类&#xff1a;String、StringBuilder、StringBuffer 接下来看看这三类常见用…...

深入Linux系列之进程地址空间

深入Linux系列之进程地址空间 1.引入 那么在之前的学习中&#xff0c;我们知道我们创建一个子进程的话&#xff0c;我们可以在代码层面调用fork函数来创建我们的子进程&#xff0c;那么fork函数的返回值根据我们当前所处进程的上下文是返回不同的值&#xff0c;它在父进程中返…...

HAL库外设宝典:基于CubeMX的STM32开发手册(持续更新)

目录 前言 GPIO&#xff08;通用输入输出引脚&#xff09; 推挽输出模式 浮空输入和上拉输入模式 GPIO其他模式以及内部电路原理 输出驱动器 输入驱动器 中断 外部中断&#xff08;EXTI&#xff09; 深入中断&#xff08;内部机制及原理&#xff09; 外部中断/事件控…...

网络安全-HSTS

什么是HSTS&#xff1f; HTTP严格传输安全协议&#xff08;HTTP Strict Transport Security&#xff0c;简称&#xff1a;HSTS&#xff09; 是互联网安全策略机制。网站可以选择使用HSTS策略&#xff0c;来让浏览器强制使用HTTPS与网站进行通信&#xff0c;以减少会话劫持风险。…...

全程Kali linux---CTFshow misc入门(38-50)

第三十八题&#xff1a; ctfshow{48b722b570c603ef58cc0b83bbf7680d} 第三十九题&#xff1a; 37换成1&#xff0c;36换成0&#xff0c;就得到长度为287的二进制字符串&#xff0c;因为不能被8整除所以&#xff0c;考虑每7位转换一个字符&#xff0c;得到flag。 ctfshow{5281…...

HarmonyOS:时间日期国际化

一、使用场景 在不同的国家和文化中&#xff0c;时间和日期格式的表示方法有所不同&#xff0c;使用惯例的不同点包括&#xff1a;日期中年月日的顺序、时间中时分秒的分隔符等。若应用中需展示时间日期&#xff0c;要确保界面以合适的方式显示&#xff0c;以便用户能够理解。 …...

使用miniforge代替miniconda

conda作为Python数据科学领域的常用软件&#xff0c;是对Python环境及相关依赖进行管理的经典工具&#xff0c;通常集成在anaconda或miniconda等产品中供用户日常使用。 但长久以来&#xff0c;conda在很多场景下运行缓慢卡顿、库解析速度过慢等问题也一直被用户所诟病&#xf…...

LIMO:少即是多的推理

25年2月来自上海交大、SII 和 GAIR 的论文“LIMO: Less is More for Reasoning”。 一个挑战是在大语言模型&#xff08;LLM&#xff09;中的复杂推理。虽然传统观点认为复杂的推理任务需要大量的训练数据&#xff08;通常超过 100,000 个示例&#xff09;&#xff0c;但本文展…...