【Linux】条件变量封装类及环形队列的实现

news2025/4/7 0:04:44

📢博客主页:https://blog.csdn.net/2301_779549673
📢博客仓库:https://gitee.com/JohnKingW/linux_test/tree/master/lesson
📢欢迎点赞 👍 收藏 ⭐留言 📝 如有错误敬请指正!
📢本文由 JohnKi 原创,首发于 CSDN🙉
📢未来很长,值得我们全力奔赴更美好的生活✨

在这里插入图片描述

在这里插入图片描述

文章目录

  • 📢前言
  • 🏳️‍🌈一、条件变量 Cond
    • 1.1 条件变量的本质
    • 1.2 核心作用
  • 🏳️‍🌈二、条件变量封装类
    • 2.1 类定义
    • 2.2 构造函数
    • 2.3 Wait 方法
    • 2.4 Notify 和 NotifyAll
    • 2.5 析构函数
    • 2.6 整体代码
    • 2.7 示例用法
  • 🏳️‍🌈三、POSIX 信号量
    • 3.1 Sem类核心功能
    • 3.2 设计意义
    • 3.3 Sem类封装
  • 🏳️‍🌈四、环形队列的逻辑及实现
    • 4.1 核心设计
    • 4.2 成员变量
    • 4.3 核心接口
      • 4.3.1 构造函数
      • 4.3.2Equeue(生产者接口)
      • 4.3.3 Pop(消费者接口)​
    • 4.4 同步机制
      • 4.4.1 信号量控制
      • 4.4.2 互斥锁保护
    • 4.5 整体代码
    • 4.6 测试代码
  • 👥总结


📢前言

紧接上回 BlockQueue生产消费模型 的实现,这篇文章,笔者来介绍一下 条件变量 的意义和作用,然后进行 封装,并实现 BlockQueue 的加强版 环形阻塞队列 RingBuffer


🏳️‍🌈一、条件变量 Cond

1.1 条件变量的本质

条件变量 是 ​线程间的通信机制,用于在 ​特定条件不满足时挂起线程,并在条件满足时 ​唤醒线程继续执行。它必须与 ​**互斥锁(Mutex)**​ 配合使用,确保操作的原子性。

1.2 核心作用

  1. 避免忙等待(Busy Waiting)​
    ​问题:没有条件变量时,线程需反复检查条件是否满足(while(条件不满足)),浪费CPU资源。
    ​解决:条件变量让线程在条件不满足时主动休眠,直到被其他线程唤醒。

  2. 实现线程间协作
    ​生产者-消费者模型:生产者通知消费者“数据已准备好”,消费者通知生产者“缓冲区有空位”。
    ​任务队列:工作线程在队列为空时休眠,主线程添加任务后唤醒工作线程。

  3. 保证操作的原子性
    通过 ​互斥锁 + 条件变量 的组合,确保线程在检查条件和进入等待状态的整个过程是原子的避免竞态条件

🏳️‍🌈二、条件变量封装类

头文件和命名空间

  • 包含 和 <pthread.h>,后者提供 POSIX 线程操作。
  • 使用 LockModule 命名空间中的 Mutex 类,表示互斥锁。

2.1 类定义

class Cond {
public:
    Cond();
    void Wait(Mutex &mutex);
    void Notify();
    void NotifyAll();
    ~Cond();
private:
    pthread_cond_t _cond;
};

封装了 pthread_cond_t,提供初始化、等待、通知和销毁功能

2.2 构造函数

Cond() {
    int n = ::pthread_cond_init(&_cond, nullptr);
    (void)n; // 忽略返回值
}
  • 调用 pthread_cond_init 初始化条件变量。
  • 返回值 n 被忽略,实际应用中应检查错误(如返回非零表示失败)。

2.3 Wait 方法

void Wait(Mutex &mutex) {
    int n = ::pthread_cond_wait(&_cond, mutex.LockPtr());
}

作用:使当前线程等待条件变量,并释放关联的互斥锁。
​参数Mutex 对象的引用,调用其 LockPtr() 获取底层 pthread_mutex_t*
​流程

  1. 调用线程必须已锁定 mutex
  2. pthread_cond_wait 自动释放 mutex 并阻塞,直到被唤醒。
  3. 被唤醒后重新获取 mutex,继续执行。

2.4 Notify 和 NotifyAll

void Notify() {
    ::pthread_cond_signal(&_cond); // 唤醒一个等待线程
}
void NotifyAll() {
    ::pthread_cond_broadcast(&_cond); // 唤醒所有等待线程
}

区别

  • Notify() 唤醒至少一个等待线程。
  • NotifyAll() 唤醒所有等待线程。
  • 应在持有相同互斥锁时调用,以避免竞态条件

2.5 析构函数

~Cond() {
    ::pthread_cond_destroy(&_cond); // 销毁条件变量
}

确保没有线程等待时销毁,否则行为未定义

2.6 整体代码

Mutex.hpp

#pragma once
#include <iostream>
#include <pthread.h> // POSIX线程库头文件

namespace LockModule
{
    // 互斥锁封装类(不可拷贝构造/赋值)
    class Mutex
    {
    public:
        // 禁止拷贝(保护系统锁资源)
        Mutex(const Mutex&) = delete;
        const Mutex& operator = (const Mutex&) = delete;

        // 构造函数:初始化POSIX互斥锁
        Mutex()
        {
            // 初始化互斥锁属性为默认值
            int n = ::pthread_mutex_init(&_lock, nullptr);
            (void)n; // 实际开发建议处理错误码
        }

        // 析构函数:销毁锁资源
        ~Mutex()
        {
            // 确保锁已处于未锁定状态
            int n = ::pthread_mutex_destroy(&_lock);
            (void)n; // 生产环境应检查返回值
        }

        // 加锁操作(阻塞直至获取锁)
        void Lock()
        {
            // 可能返回EDEADLK(死锁检测)等错误码
            int n = ::pthread_mutex_lock(&_lock);
            (void)n; // 简化处理,实际建议抛异常或记录日志
        }

        // 解锁操作(必须由锁持有者调用)
        void Unlock()
        {
            // 未持有锁时解锁将返回EPERM
            int n = ::pthread_mutex_unlock(&_lock);
            (void)n; 
        }

        // 获取底层锁指针(可用于自定义条件变量)
        pthread_mutex_t *LockPtr()
        {
            return &_lock;
        }

    private:
        pthread_mutex_t _lock; // 底层锁对象
    };

    // RAII锁守卫(自动管理锁生命周期)
    class LockGuard
    {
    public:
        // 构造时加锁(必须传入已初始化的Mutex引用)
        LockGuard(Mutex &mtx):_mtx(mtx)
        {
            _mtx.Lock(); // 进入临界区
        }

        // 析构时自动解锁(异常安全保证)
        ~LockGuard()
        {
            _mtx.Unlock(); // 离开作用域自动释放
        }

    private:
        Mutex &_mtx; // 引用方式持有,避免拷贝导致未定义行为
    };
}

Cond.hpp

#pragma once

#include <iostream>
#include <pthread.h>

#include "Mutex.hpp"


namespace CondModule{
    using namespace LockModule;

    class Cond{
        public:
            Cond(){
                int n = ::pthread_cond_init(&_cond, nullptr);
                (void)n;
            }

            void Wait(Mutex& mutex){
                // pthread_mutex_lock() 的作用是 锁定互斥锁,直到成功为止。
                // 第一个参数 cond 是一个条件变量的标识符,第二个参数 mutex 是一个互斥锁的标识符。
                // pthread_cond_wait() 用来等待条件变量 cond 被 pthread_cond_signal() 或 pthread_cond_broadcast() 唤醒。
                // 它将阻塞调用线程,直到被唤醒或被中断。
                // 返回值是 0 表示成功,其他值表示出错。
                // 出错时,errno 被设置。
                // 成功时,调用线程获得互斥锁 mutex,直到被 pthread_cond_signal() 或 pthread_cond_broadcast() 唤醒。
                int n = ::pthread_cond_wait(&_cond, mutex.LockPtr());
                (void)n;
            }

            void Notify(){
                // pthread_cond_signal() 用来唤醒一个正在等待条件变量的线程。
                // 第一个参数 cond 是一个条件变量的标识符。
                // 返回值是 0 表示成功,其他值表示出错。
                // 出错时,errno 被设置。
                // 成功时,一个正在等待条件变量的线程被唤醒。
                int n = ::pthread_cond_signal(&_cond);
                (void)n;
            }

            void NotifyAll(){
                // pthread_cond_broadcast() 用来唤醒所有正在等待条件变量的线程。
                // 第一个参数 cond 是一个条件变量的标识符。
                // 返回值是 0 表示成功,其他值表示出错。
                // 出错时,errno 被设置。
                // 成功时,所有正在等待条件变量的线程被唤醒。
                int n = ::pthread_cond_broadcast(&_cond);
                (void)n;
            }

            ~Cond(){
                int n = ::pthread_cond_destroy(&_cond);
                (void)n;
            }
        
        private:
            pthread_cond_t _cond;
    };
}

2.7 示例用法

Mutex mutex;
Cond cond;
std::queue<int> buffer;

// 生产者线程
void producer() {
    mutex.Lock();
    while (buffer.full()) {
        cond.Wait(mutex); // 等待缓冲区非满
    }
    buffer.push(1);
    cond.Notify(); // 通知消费者
    mutex.Unlock();
}

// 消费者线程
void consumer() {
    mutex.Lock();
    while (buffer.empty()) {
        cond.Wait(mutex); // 等待缓冲区非空
    }
    buffer.pop();
    cond.Notify(); // 通知生产者
    mutex.Unlock();
}

🏳️‍🌈三、POSIX 信号量

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

因此我们可以封装一个 Sem类 来统筹管理 POSIX信号量

扮演 线程同步协调者 的角色,通过 信号量机制 精准控制生产与消费的节奏,确保缓冲区的线程安全与高效运作。其设计不仅简化了复杂的同步逻辑,还通过 封装 和 *RAII机制 *提升了代码的可靠性和可维护性,是多线程编程中资源同步的典范实现。

3.1 Sem类核心功能

用于简化 POSIX 信号量的使用。其主要功能如下:

  • 初始化:创建并初始化信号量。
  • P 操作​(P()):申请资源(信号量减 1),若资源不足则阻塞。
  • V 操作​(V()):释放资源(信号量加 1),唤醒等待线程。
  • 销毁:清理信号量资源。

3.2 设计意义

  1. 简化信号量操作
  • 封装底层 API:将 sem_initsem_waitsem_postsem_destroy 封装为类方法,隐藏实现细节。
  • 统一接口:通过 P()V() 提供直观的语义(“申请”和“释放”)。
  1. 提高代码可维护性
    ​RAII 机制:构造函数初始化资源,析构函数自动释放,避免资源泄漏。
{
    Sem sem(5); // 信号量初始化
    sem.P();    // 使用信号量
}               // 作用域结束自动调用 ~Sem()
  1. 支持多种同步场景
    通过调整初始值 value,可适配不同场景:
  • 互斥锁(Mutex)​:value = 1(二元信号量)。
  • 资源池:value = N(表示最多允许 N 个线程同时访问资源)。
  • 生产者-消费者模型:通过两个信号量分别控制缓冲区空间和数据数量。

3.3 Sem类封装

#pragma once
#include <semaphore.h>

namespace SemModule{
    int defaultsemval = 1;
    class Sem{
        public:
            Sem(int value = defaultsemval) : _init_value(value){
                sem_init(&sem, 0, value);
            }

            void P(){
                ::sem_wait(&sem);
            }

            void V(){
                ::sem_post(&sem);
            }

            ~Sem(){
                ::sem_destroy(&sem);
            }

        private:
            sem_t sem;
            int _init_value;
    };
}

🏳️‍🌈四、环形队列的逻辑及实现

在这里插入图片描述

4.1 核心设计

这是一个基于 ​信号量(Semaphore)​ 和 ​互斥锁(Mutex)​ 的线程安全环形队列(Ring Buffer),用于实现 ​生产者-消费者模型。通过以下机制确保线程安全:

​信号量控制:空间信号量(_spacesem)控制可用空间,数据信号量(_datasem)控制可消费数据。
​互斥锁保护:生产者和消费者各自拥有独立的锁(_p_lock_c_lock),支持多生产者和多消费者并发操作。

4.2 成员变量

在这里插入图片描述

4.3 核心接口

4.3.1 构造函数

RingBuffer(int cap)
    : _ring(cap),         // 初始化缓冲区容量
      _cap(cap),          // 记录总容量
      _p_step(0),         // 生产者起始位置
      _c_step(0),         // 消费者起始位置
      _datasem(0),        // 初始无可消费数据
      _spacesem(cap) {}   // 初始空间=总容量

作用:初始化缓冲区及相关同步机制

4.3.2Equeue(生产者接口)

void Equeue(const T &in) {
    _spacesem.P();        // 等待可用空间(信号量-1)
    {
        LockGuard lockguard(_p_lock);  // 加生产者锁
        _ring[_p_step] = in;           // 写入数据
        _p_step = (_p_step + 1) % _cap; // 环形移动
    }
    _datasem.V();         // 增加可消费数据(信号量+1)
}

流程

  1. 申请空间:通过 _spacesem.P() 等待缓冲区有空位。
  2. 生产数据:在锁保护下写入数据并更新生产者索引。
  3. 通知消费者:通过 _datasem.V() 增加可消费数据数量。

4.3.3 Pop(消费者接口)​

void Pop(T *out) {
    _datasem.P();         // 等待可消费数据(信号量-1)
    {
        LockGuard lockguard(_c_lock);  // 加消费者锁
        *out = _ring[_c_step];         // 读取数据
        _c_step = (_c_step + 1) % _cap; // 环形移动
    }
    _spacesem.V();        // 增加可用空间(信号量+1)
}

流程

  1. ​申请数据:通过 _datasem.P() 等待缓冲区有数据。
  2. 消费数据:在锁保护下读取数据并更新消费者索引。
  3. 通知生产者:通过 _spacesem.V() 增加可用空间数量。

4.4 同步机制

4.4.1 信号量控制

在这里插入图片描述

  • ​生产者阻塞条件_spacesem 为0时(缓冲区满)。
  • 消费者阻塞条件_datasem 为0时(缓冲区空)。

4.4.2 互斥锁保护

在这里插入图片描述

4.5 整体代码

#include "Cond.hpp"
#include "Mutex.hpp"
#include "Sem.hpp"

#include <vector>
#include <pthread.h>


namespace RingBufferModule{

    using namespace LockModule;
    using namespace CondModule;
    using namespace SemModule;

    template<typename T>
    class RingBuffer{
        public:
            RingBuffer(int cap)
                : _ring(cap),       // 初始化缓冲区容量
                _cap(cap),          // 记录总容量
                _p_step(0),         // 生产者起始位置
                _c_step(0),         // 消费者起始位置
                _datasem(0),        // 初始无可消费数据
                _spacesem(cap)      // 初始空间=总容量
            {}

            // 生产者接口
            void Equeue(const T &in) {
                _spacesem.P();        // 等待可用空间(信号量-1)
                {
                    LockGuard lockguard(_p_lock);  // 加生产者锁
                    _ring[_p_step] = in;           // 写入数据
                    _p_step = (_p_step + 1) % _cap; // 环形移动
                }
                _datasem.V();         // 增加可消费数据(信号量+1)
            }

            // 消费者接口
            void Pop(T *out) {
                _datasem.P();         // 等待可消费数据(信号量-1)
                {
                    LockGuard lockguard(_c_lock);  // 加消费者锁
                    *out = _ring[_c_step];         // 读取数据
                    _c_step = (_c_step + 1) % _cap; // 环形移动
                }
                _spacesem.V();        // 增加可用空间(信号量+1)
            }

            // 析构函数
            ~RingBuffer(){}

        private:
            std::vector<T> _ring;      // 环,临界资源
            int _cap;                   // 环的容量 
            int _p_step;                // 生产者指针位置
            int _c_step;                // 消费者指针位置

            Mutex _p_lock;              // 生产者锁
            Mutex _c_lock;              // 消费者锁

            Sem _datasem;               // 数据信号量
            Sem _spacesem;              // 空间信号量
    };
}

4.6 测试代码

#include "RingBuffer.hpp"
#include <pthread.h>
#include <unistd.h>
#include <ctime>

using namespace RingBufferModule;

void *Consumer(void *args)
{
    RingBuffer<int> *ring_buffer = static_cast<RingBuffer<int> *>(args);
    while(true)
    {
        sleep(1);
        // sleep(1);
        // 1. 消费数据
        int data;
        ring_buffer->Pop(&data);

        // 2. 处理:花时间
        std::cout << "消费了一个数据: " << data << std::endl;
    }
}

void *Productor(void *args)
{
    RingBuffer<int> *ring_buffer = static_cast<RingBuffer<int> *>(args);
    int data = 0;
    while (true)
    {
        // 1. 获取数据:花时间
        // sleep(1);

        // 2. 生产数据
        ring_buffer->Equeue(data);
        std::cout << "生产了一个数据: " << data << std::endl;
        data++;
    }
}

int main()
{
    RingBuffer<int> *ring_buffer = new RingBuffer<int>(5); // 共享资源 -> 临界资源
    // 单生产,单消费
    pthread_t c1, p1, c2,c3,p2;
    pthread_create(&c1, nullptr, Consumer, ring_buffer);
    pthread_create(&c2, nullptr, Consumer, ring_buffer);
    pthread_create(&c3, nullptr, Consumer, ring_buffer);
    pthread_create(&p1, nullptr, Productor, ring_buffer);
    pthread_create(&p2, nullptr, Productor, ring_buffer);


    pthread_join(c1, nullptr);
    pthread_join(c2, nullptr);
    pthread_join(c3, nullptr);
    pthread_join(p1, nullptr);
    pthread_join(p2, nullptr);


    delete ring_buffer;

    return 0;
}

👥总结

本篇博文对 【Linux】条件变量封装类及环形队列的实现 做了一个较为详细的介绍,不知道对你有没有帮助呢

觉得博主写得还不错的三连支持下吧!会继续努力的~

请添加图片描述

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

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

相关文章

离线部署kubesphere(已有k8s和私有harbor的基础上)

前言说明&#xff1a;本文是在已有k8s集群和私有仓库harbor上进行离线安装kubesphere&#xff1b;官网的离线教程写都很详细&#xff0c;但是在部署部份把搭建集群和搭建仓库也写一起了&#xff0c;跟着做踩了点坑&#xff0c;这里就记录下来希望可以帮助到需要的xdm。 1.根据官…

非阻塞IO,fcntl,多路转接,select,poll,epoll,reactor

IO次数会影响程序的效率&#xff0c;在编程中往往会尽量减少IO次数&#xff0c;用以提高程序的效率&#xff0c;例如缓冲区,就是减少IO次数提高效率的一种方式&#xff1b;而IO影响效率的最大原因其实是因为IO等拷贝&#xff0c;在进行IO时往往需要拷贝的数据就绪&#xff0c;或…

Redis常用的数据结构及其使用场景

字符串(String) string 是 redis 最基本的类型&#xff0c;你可以理解成与 Memcached 一模一样的类型&#xff0c;一个 key 对应一个 value。 string 类型是二进制安全的。意思是 redis 的 string 可以包含任何数据&#xff0c;比如jpg图片或者序列化的对象。 string 类型是 R…

PhotoShop学习04

1.背景图层 最下面的被锁锁住的图层为背景图层&#xff0c;背景图层充当整个图层的背景&#xff0c;名字标注为背景&#xff0c;无法修改背景图层的排序始终位于图层最底部。 当我想把上方的图层移动到背景图层之后&#xff0c;发现无法移动图层无法移动&#xff0c;把背景图层…

服务器有2张显卡,在别的虚拟环境部署运行了Xinference,然后又建个虚拟环境再部署一个可以吗?

环境: 云服务器Ubuntu系统 2张 NVIDIA H20 96GB Qwen2.5-VL-72B-Instruct-AWQ Qint4量化 AWQ 是 “Activation - Aware Weight Quantization” 的缩写,即激活感知权重量化。它是一种针对大型模型的先进量化算法,通过在权重量化过程中引入对激活值的感知,最小化量化误差…

K8s中CPU和Memory的资源管理

资源类型 在 Kubernetes 中&#xff0c;Pod 作为最小的原子调度单位&#xff0c;所有跟调度和资源管理相关的属性都属于 Pod。其中最常用的资源就是 CPU 和 Memory。 CPU 资源 在 Kubernetes 中&#xff0c;一个 CPU 等于 1 个物理 CPU 核或者一个虚拟核&#xff0c;取决于节…

任务挂起和恢复

任务挂起和恢复API函数 下面用按键和震动传感器验证任务挂起和恢复API函数&#xff1a; PA7接震动传感器&#xff0c;按键引脚为PA0&#xff0c;提前初始化好GPIO引脚 key.c #include "key.h" #include "stm32f10x.h"void KeyInit() {GPIO_InitTypeDef …

【NLP 55、投机采样加速推理】

目录 一、投机采样 二、投机采样改进&#xff1a;美杜莎模型 流程 改进 三、Deepseek的投机采样 流程 Ⅰ、输入文本预处理 Ⅱ、引导模型预测 Ⅲ、候选集筛选&#xff08;可选&#xff09; Ⅳ、主模型验证 Ⅴ、生成输出与循环 骗你的&#xff0c;其实我在意透了 —— 25.4.4 一、…

如何在 Windows 上安装 Python

Python是一种高级编程语言&#xff0c;由于其简单性、多功能性和广泛的应用范围而变得越来越流行。如何在 Windows 操作系统中安装 Python 的过程相对简单&#xff0c;只需几个简单的步骤。 本文旨在指导您完成在 Windows 计算机上下载和安装 Python 的过程。 如何在 Windows…

selectdb修改表副本

如果想修改doris&#xff08;也就是selectdb数据库&#xff09;表的副本数需要首先确定是否分区表&#xff0c;当前没有数据字典得知哪个表是分区的&#xff0c;只能先show partitions看结果 首先&#xff0c;副本数不应该大于be节点数 其次&#xff0c;修改期间最好不要跑业务…

Metabase:一个免费开源的BI平台

今天给大家介绍一个开源数据可视化分析工具&#xff1a;Metabase。它可以帮助用户快速连接数据库、执行查询并创建交互式仪表盘&#xff0c;即使非技术人员也能快速上手。 Metabase 支持多种数据源&#xff0c;包括 MySQL、PostgreSQL、Oracle、SQL Server、SQLite、MongoDB、P…

第15届蓝桥杯省赛python组A,B,C集合

过几天就省赛了&#xff0c;一直以来用的是C&#xff0c;Python蓝桥杯也是刚刚开始准备&#xff08;虽然深度学习用的都是python&#xff0c;但是两者基本没有任何关系&#xff09;&#xff0c;这两天在做去年题时犯了很多低级错误&#xff0c;因此记录一下以便自己复查 PS&am…

为什么有的深度学习训练,有训练集、验证集、测试集3个划分,有的只是划分训练集和测试集?

在机器学习和深度学习中&#xff0c;数据集的划分方式取决于任务需求、数据量以及模型开发流程的严谨性。 1. 三者划分&#xff1a;训练集、验证集、测试集 目的 训练集&#xff08;Training Set&#xff09;&#xff1a;用于模型参数的直接训练。验证集&#xff08;Validati…

虚拟现实 UI 设计:打造沉浸式用户体验

VR UI 设计基础与特点 虚拟现实技术近年来发展迅猛&#xff0c;其独特的沉浸式体验吸引了众多领域的关注与应用。在 VR 环境中&#xff0c;UI 设计扮演着至关重要的角色&#xff0c;它是用户与虚拟世界交互的桥梁。与传统 UI 设计相比&#xff0c;VR UI 设计具有显著的特点。传…

前端Uniapp接入UviewPlus详细教程!!!

相信大家在引入UviewPlusUI时遇到很头疼的问题&#xff0c;那就是明明自己是按照官网教程一步一步的走&#xff0c;为什么到处都是bug呢&#xff1f;今天我一定要把这个让人头疼的问题解决了&#xff01; 1.查看插件市场 重点&#xff1a; 我们打开Dcloud插件市场搜素uviewPl…

【性能优化点滴】odygrd/quill在编译期做了哪些优化

Quill 是一个高性能的 C 日志库&#xff0c;它在编译器层面进行了大量优化以确保极低的运行时开销。以下是 Quill 在编译器优化方面的关键技术和实现细节&#xff1a; 1. 编译时字符串解析与格式校验 Quill 在编译时完成格式字符串的解析和校验&#xff0c;避免运行时开销&…

02 反射 泛型(II)

目录 一、反射 1. 反射引入 2. 创建对象 3. 反射核心用法 二、泛型 1. 泛型的重要性 &#xff08;1&#xff09;解决类型安全问题 &#xff08;2&#xff09;避免重复代码 &#xff08;3&#xff09;提高可读性和维护性 2. 泛型用法 &#xff08;1&#xff09;泛型类 …

元宇宙浪潮下,前端开发如何“乘风破浪”?

一、元宇宙对前端开发的新要求 元宇宙的兴起&#xff0c;为前端开发领域带来了全新的挑战与机遇。元宇宙作为一个高度集成、多维互动的虚拟世界&#xff0c;要求前端开发不仅具备传统网页开发的能力&#xff0c;还需要掌握虚拟现实&#xff08;VR&#xff09;、增强现实&#…

2025年3月 Scratch 图形化(二级)真题解析 中国电子学会全国青少年软件编程等级考试

2025.03Scratch图形化编程等级考试二级真题试卷 一、选择题 第 1 题 甲、乙、丙、丁、戊五人参加100米跑比赛&#xff0c;甲说:“我的前面至少有两人&#xff0c;但我比丁快。”乙说:“我的前面是戊。”丙说:“我的后面还有两个人。”请从前往后&#xff08;按照速度快慢&a…

从代码学习深度学习 - GRU PyTorch版

文章目录 前言一、GRU模型介绍1.1 GRU的核心机制1.2 GRU的优势1.3 PyTorch中的实现二、数据加载与预处理2.1 代码实现2.2 解析三、GRU模型定义3.1 代码实现3.2 实例化3.3 解析四、训练与预测4.1 代码实现(utils_for_train.py)4.2 在GRU.ipynb中的使用4.3 输出与可视化4.4 解析…