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

【Linux】寿司线程池{单例模式之懒汉模式下的线程池}

文章目录

  • 回顾单例模式
  • 0.多线程下的单例模式的意义
  • 1.什么是单例模式
    • 1.0设计模式
    • 1.1C++单例模式的介绍及原理
    • 1.2拷贝构造和赋值重载的处理
    • 1.3if (nullptr == ptr),nullptr放在比较运算符的前面?
    • 1.4实现单例模式的方式
  • 2.实现懒汉方式的单例模式
    • 2.1单线程的单例模式
    • 2.2多线程的单例模式
  • 3.总结线程池
  • 4.代码
    • singleThreadPool.hpp
    • singleTestMain.cc

回顾单例模式

在cpp专栏,我们已经讲过单例模式。特殊类设计[下] — 单例模式

0.多线程下的单例模式的意义

在多线程环境下,单例模式的目的是确保无论有多少线程尝试访问或创建类的实例,都只有一个实例被创建,并且这个实例可以被所有线程共享和访问。这是因为在多线程编程中,如果没有适当的同步机制,多个线程可能同时尝试创建类的实例,从而导致产生多个实例,这与单例模式的初衷相违背。

单例模式在多线程环境下的实现需要特别注意线程安全性。如果没有正确的同步,多个线程可能会同时检查单例是否已经被创建,并且都得出否定的结论,从而都尝试创建新的实例。这会导致产生多个实例,破坏单例模式的约束。

因此,多线程下的单例模式的主要目的是:

确保唯一性:不论何时何地,整个应用程序范围内都只有一个类的实例存在。
线程安全:确保在多线程环境下,类的实例的创建和访问都是安全的,不会出现数据竞争或不一致的情况。
全局访问点:提供一个全局的访问点来获取这个唯一的实例,使得任何需要的地方都可以方便地获取和使用这个实例。
为了实现线程安全的单例模式,通常需要使用互斥锁(如std::mutex)或其他同步机制来确保在创建实例时的线程安全。这样,即使多个线程同时尝试访问或创建单例,也只有一个线程能够成功创建实例,而其他线程将获取已经创建好的实例。

1.什么是单例模式

1.0设计模式

在这里插入图片描述

IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式

1.1C++单例模式的介绍及原理

单例模式是一种 “经典的, 常用的, 常考的” 设计模式.

单例模式是一种设计模式,它保证一个类仅有一个实例,并提供一个全局访问点来访问该实例。这种模式在许多场景下都很有用,例如,你可能需要一个唯一的配置管理器、资源管理器或线程池。

在C++中,实现单例模式的基本原理如下:

将类的构造函数设为私有,以防止外部代码通过new来创建类的实例。
在类内部提供一个静态的私有成员变量来保存类的唯一实例。
提供一个公开的静态成员函数(通常命名为getInstance或Instance),用于返回类的唯一实例。如果实例尚未创建,该函数将负责创建它。
实现一个简易的单例模式至少要具备的条件

要实现一个简易的单例模式,你需要满足以下条件:

私有构造函数:确保类的实例不能从类外部被创建。
静态私有成员变量:用于存储类的唯一实例。
公开的静态成员函数:用于获取类的唯一实例。这个函数应该在第一次被调用时创建实例,并在后续的调用中返回已创建的实例。
以下是一个简单的C++用懒汉模式的单例模式实现示例:

cpp
class Singleton {  
private:  // 私有构造函数,防止外部创建实例  Singleton() {}  // 静态私有成员变量,保存唯一实例  static Singleton* instance;  public:  // 公开的静态成员函数,用于获取唯一实例  static Singleton* getInstance() {  if (!instance) {  instance = new Singleton();  }  return instance;  }  // 假设的某个成员函数  void someFunction() {  // ...  }  // 析构函数设为公有,但通常不需要外部直接调用  ~Singleton() {  // 清理资源  }  
};  
// 初始化静态成员变量  
Singleton* Singleton::instance = nullptr;

在这个示例中,Singleton类只有一个私有构造函数,因此不能从类外部创建其实例。getInstance函数是获取Singleton类唯一实例的入口点。如果instance为nullptr(即尚未创建实例),则getInstance会创建一个新的实例。在后续调用中,getInstance将直接返回已创建的实例。

注意:上述实现不是线程安全的。在多线程环境下,多个线程可能同时检查instance是否为nullptr并尝试创建实例,从而导致创建多个实例。为了解决这个问题,可以使用互斥锁(如std::mutex)或其他同步机制来确保在任意时刻只有一个线程可以执行创建实例的代码。此外,为了避免内存泄漏,通常还需要提供一个全局的清理函数来删除单例实例。

1.2拷贝构造和赋值重载的处理

在C++单例模式的实现中,拷贝构造函数和赋值操作符通常是不需要的,因此将它们设为私有并禁止外部使用是一个常见的做法。这是因为单例模式要求一个类只有一个实例,而拷贝构造和赋值操作会允许创建类的多个实例,这与单例模式的原则相违背。

对于拷贝构造函数,你可以这样处理:

cpp
class Singleton {  
private:  // 禁止拷贝构造  Singleton(const Singleton&) = delete;  // ... 其他成员 ...  
};

对于赋值操作符,你也可以禁止它:

cpp
class Singleton {  
private:  // 禁止赋值操作  Singleton& operator=(const Singleton&) = delete;  // ... 其他成员 ...  
};

= delete是C++11引入的特性,用于显式地禁止某个成员函数的使用。编译器将尝试调用这些函数时会产生编译错误。

通过禁止拷贝构造和赋值操作,你确保了单例模式的正确性,因为即使尝试进行拷贝或赋值,编译器也会阻止这样的操作。

此外,对于单例模式来说,还需要考虑线程安全的问题。在多线程环境下,需要确保getInstance函数的线程安全性,以避免多个线程同时创建实例。这通常可以通过使用互斥锁(如std::mutex)或其他同步机制来实现。

最后,为了防止内存泄漏,你可能还需要在程序结束时删除单例实例。这可以通过在单例类中添加一个静态的清理函数来实现,该函数在程序结束时调用单例实例的析构函数。但是,请注意,在现代C++中,通常依赖智能指针(如std::shared_ptr)和程序终止时的全局对象析构顺序来自动管理单例实例的生命周期,而不需要显式地编写清理代码。

1.3if (nullptr == ptr),nullptr放在比较运算符的前面?

在C++中,将nullptr放在比较运算符==或!=的前面是一种良好的编程习惯,尤其是在与指针进行比较时。这样做的好处主要体现在两个方面:

防止误写为赋值运算符:
将nullptr放在前面可以防止你不小心将比较运算符==误写为赋值运算符=。如果nullptr在后面,例如ptr = nullptr,当你意图进行比较而误写为赋值时,编译器不会产生错误,但代码的逻辑会完全改变。这可能导致难以察觉的逻辑错误。通过将nullptr放在前面,你可以更容易地识别和纠正这种错误,因为nullptr = ptr这样的写法在语法上是错误的,编译器会立即报错。

代码一致性和可读性:
将常量放在比较运算符的左边可以增强代码的一致性。在C++中,常量通常应该放在比较运算符的左侧,这样可以使代码看起来更加统一和整洁。此外,这种写法也符合一些编程规范和风格指南,有助于提高代码的可读性。

举个例子,假设你有一个指针ptr,你应该这样写比较语句:

cpp
if (nullptr == ptr) {  // ptr 是 nullptr  
}
而不是这样:cpp
if (ptr == nullptr) {  // ptr 是 nullptr  
}

虽然第二种写法在逻辑上是正确的,但第一种写法提供了额外的安全性,防止了可能的误写。

然而,值得注意的是,现代编译器和IDE通常都有强大的代码检查和重构工具,可以帮助你避免这类错误。因此,即使你将nullptr放在比较运算符的右侧,只要你的编程环境配置得当,也可以有效地避免误写为赋值运算符的问题。不过,遵循将常量放在比较运算符左侧的约定仍然是一个好习惯,因为它可以提高代码的一致性和可读性。

1.4实现单例模式的方式

饿汉实现方式和懒汉实现方式

吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭。不用的时候就准备好了,要用的时候直接拿来用。
吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式.懒汉方式最核心的思想是 “延时加载”. 从而能够优化服务器的启动速度。用的时候再准备。

2.实现懒汉方式的单例模式

2.1单线程的单例模式

static singleThreadPool<T> *getThreadPoolInstance(int threadNum = g_threadNum)
{if (nullptr == ptrThreadPool){ptrThreadPool = new singleThreadPool<T>(threadNum);}return ptrThreadPool;
}

2.2多线程的单例模式

static singleThreadPool<T> *getThreadPoolInstance(int threadNum = g_threadNum)
{if (nullptr == ptrThreadPool){ptrThreadPool = new singleThreadPool<T>(threadNum);}return ptrThreadPool;
}
  1. 单线程下的单例模式代码拿到多线程场景中,需要考虑的【线程安全问题】只发生在第一次创建单例时,即第一次可能有多个线程在执行if语句,这样就可能有不止一个线程获取到了单例,基于这种情况,我们进行了第一步改版。如下。
static singleThreadPool<T> *getThreadPoolInstance(int threadNum = g_threadNum)
{{lockGuard lockguard(&mutex);if (nullptr == ptrThreadPool){ptrThreadPool = new singleThreadPool<T>(threadNum);}}return ptrThreadPool;
}
  1. 这样并不完全对。我们还要考虑,第一次的某个线程成功获取了单例,即这个线程拿着指向一个单例对象的ptrThreadPool走了,之后的线程再次调用getThreadPoolInstance接口想获取到这个唯一的单例时,需要申请锁–执行if–if不满足–解锁–返回ptrThreadPool。问题来了,之后的线程虽然没有创建单例但是获取到了这个唯一的单例,即之后的线程可以获取到唯一的单例,多线程下的单例模式似乎已经搞定了。但是,这样会存在大量的申请和释放锁的行为,这毫无意义且浪费资源,即之后的线程在获取单例时,if一定是不满足的,这时压根没必要进行加锁和解锁,但是为了保证第一次创建单例是安全的,加锁的行为必须有,之后的线程申请到了锁那还好,如果申请不到还要等待⇒ 这毫无意义且浪费资源。做进一步优化如下。
static singleThreadPool<T> *getThreadPoolInstance(int threadNum = g_threadNum){if (nullptr == ptrThreadPool){lockGuard lockguard(&mutex);if (nullptr == ptrThreadPool){ptrThreadPool = new singleThreadPool<T>(threadNum);}}return ptrThreadPool;}

这样写的优势

假定【后续线程】:第一次已经有某个线程创建了单例即单例已存在。那么这样写的优势:有效减少后续线程进行加锁检测的问题,拦截大量后续线程直接访问锁的行为
在这里插入图片描述

3.总结线程池

  1. 设计一个线程池,这个线程池有多个线程在运行,一旦接收到任务,则某个线程就会去获取并执行任务。
  2. 可能有多个线程充当派发任务的一方,这时就引入了【单例模式】版的线程池,旨在让不同的线程共享唯一的一个线程池实例,任务方线程们都向这个单例线程池派发任务。
  3. 上面的代码我们只创建了一个任务派送方【main线程】,但是单例模式我们已经写好了,读者们可以创建多个任务派送方测试单例模式是否正确。
  4. 由于我们设计的主线程是死循环,我们并没有维护单例的回收,单例的回收我们在特殊类设计[下] — 单例模式已经讲过。在这里插入图片描述

4.代码

其余代码与【Linux】认识线程池 AND 手撕线程池(正常版)相同,点击获取即可。

singleThreadPool.hpp

#pragma once#include <iostream>
#include <vector>
#include <string>
#include <queue>
#include <unistd.h>
#include "thread.hpp"
#include "lockGuard.hpp"
#include "log.hpp"const int g_threadNum = 3;template <class T>
class singleThreadPool
{
public:pthread_mutex_t *getMutex(){return &lock;}void waitCond(){pthread_cond_wait(&cond, &lock);}bool isEmpty(){return _taskQueue.empty();}T getTask(){T t = _taskQueue.front();_taskQueue.pop();return t;}static void *startRoutine(void *args){ThreadInfo *td = (ThreadInfo *)args;singleThreadPool<T> *tp = (singleThreadPool<T> *)td->_ptrThreadPool;while (true){T task;{lockGuard lockguard(tp->getMutex());while (tp->isEmpty())tp->waitCond();task = tp->getTask();}task(td->_threadName);}}private:// 构造函数singleThreadPool(int thread_num = g_threadNum): _threadNum(thread_num){pthread_mutex_init(&lock, nullptr);pthread_cond_init(&cond, nullptr);for (int i = 1; i <= _threadNum; i++){// 初始化列表区域 对象还未存在 走到函数块{}内 对象已存在 可以使用this指针_threads.push_back(new Thread(i, startRoutine, this));}}singleThreadPool(const singleThreadPool<T> &copy) = delete;const singleThreadPool<T> &operator=(const singleThreadPool<T> &copy) = delete;public:// 考虑 多线程使用单例 的情况static singleThreadPool<T> *getThreadPoolInstance(int threadNum = g_threadNum){if (nullptr == ptrThreadPool){lockGuard lockguard(&mutex);if (nullptr == ptrThreadPool){ptrThreadPool = new singleThreadPool<T>(threadNum);}}return ptrThreadPool;}void run(){for (auto &iter : _threads){iter->start();// std::cout << iter->name() << " 启动成功" << std::endl;logMsg(NORMAL, "%s %s", iter->name().c_str(), "启动成功");}}void pushTask(const T &task){lockGuard lockguard(&lock);_taskQueue.push(task);pthread_cond_signal(&cond);}~singleThreadPool(){for (auto &iter : _threads){iter->join();delete iter;}pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);}private:int _threadNum;std::vector<Thread *> _threads;std::queue<T> _taskQueue;pthread_mutex_t lock;pthread_cond_t cond;volatile static singleThreadPool<T> *ptrThreadPool;懒汉是调用的时候才会赋值,如果创建了对象这个指针实际已经指向了单例,如果指针一开始被优化到寄存器里了,那第二次后续线程访问这个指针就是null,后续不从内存里读取的话就会一直是null了static pthread_mutex_t mutex;
};// 类内声明 类外初始化
template <typename T>
singleThreadPool<T> *singleThreadPool<T>::ptrThreadPool = nullptr;template <typename T>
pthread_mutex_t singleThreadPool<T>::mutex = PTHREAD_MUTEX_INITIALIZER;

singleTestMain.cc

#include <ctime>
#include <cstdlib>
#include <iostream>
#include <unistd.h>#include "singleThreadPool.hpp"
#include "Task.hpp"int main()
{srand((unsigned long)time(nullptr) ^ getpid());// ThreadPool<Task> *tp = new ThreadPool<Task>();// ThreadPool<Task> *tp = ThreadPool<Task>::getThreadPoolInstance();//tp->run();singleThreadPool<Task>::getThreadPoolInstance()->run();while (true){// 生产数据/制作任务 -- 耗费时间int x = rand() % 10 + 1;usleep(1000);int y = rand() % 5 + 1;Task t(x, y, [](int x, int y) -> int{ return x + y; });logMsg(DEBUG, "Main-Pro 发送任务: %d+%d=未知", x, y);// 推送任务到线程池中// tp->pushTask(t);singleThreadPool<Task>::getThreadPoolInstance()->pushTask(t);sleep(1);}return 0;
}

相关文章:

【Linux】寿司线程池{单例模式之懒汉模式下的线程池}

文章目录 回顾单例模式0.多线程下的单例模式的意义1.什么是单例模式1.0设计模式1.1C单例模式的介绍及原理1.2拷贝构造和赋值重载的处理1.3if (nullptr ptr)&#xff0c;nullptr放在比较运算符的前面?1.4实现单例模式的方式 2.实现懒汉方式的单例模式2.1单线程的单例模式2.2多…...

Docker资源管理和分配指南

什么是cgroup&#xff1f; cgroups其名称源自控制组群&#xff08;control groups&#xff09;的简写&#xff0c;是Linux内核的一个功能&#xff0c;用来限制、控制与分离一个进程组&#xff08;如CPU、内存、磁盘输入输出等&#xff09;。 什么是Docker资源限制&#xff1f;…...

为什么索引的底层结构是B+树

B树 1.数据库与数据交互的单位是page,而B树的每个节点都是一个page,访问一个节点&#xff0c;就相当于进行了一次I/O操作。所以访问的节点越少&#xff0c;查找效率越大。而B树是矮胖的&#xff0c;查找深度也不会太大。 2.B树中的节点是有序存储的&#xff0c;对于范围查询、排…...

NLP学习路线指南总结

当然可以&#xff0c;以下是一份较为详细的NLP学习路线指南&#xff0c;帮助你逐步掌握自然语言处理的核心技术和应用。 一、基础知识与技能 语言学基础&#xff1a; 语言学基本概念&#xff1a;语音、语法、语义等。语言的层次与分类&#xff1a;语音学、音系学、句法学、语…...

试过了,ChatGPT确实不用注册就可以使用了!

看到官网说不用登录也可以直接使用ChatGPT 我们来试一下 直接打开官网 默认是直接进入了chatgpt3.5的聊天界面 之前是默认进的登录页面 聊一下试试 直接回复了&#xff0c;目前属于未登录状态&#xff0c;挺好&#xff01; 来试下ChatGPT4 跳转到了登录页面 目前来看gpt4还…...

CANoe自带的TCP/IP协议栈中TCP的keep alive机制是如何工作的

TCP keep alive机制我们已经讲过太多次,车内很多控制器的TCP keep alive机制相信很多开发和测试的人也配置或者测试过。我们今天想知道CANoe软件自带的TCP/IP协议栈中TCP keep alive机制是如何工作的。 首先大家需要知道TCP keep alive的参数有哪些?其实就三个参数:CP_KEEP…...

【C++练级之路】【Lv.18】哈希表(哈希映射,光速查找的魔法)

快乐的流畅&#xff1a;个人主页 个人专栏&#xff1a;《算法神殿》《数据结构世界》《进击的C》 远方有一堆篝火&#xff0c;在为久候之人燃烧&#xff01; 文章目录 引言一、哈希1.1 哈希概念1.2 哈希函数1.3 哈希冲突 二、闭散列2.1 数据类型2.2 成员变量2.3 默认成员函数2.…...

「PHP系列」If...Else语句/switch语句

文章目录 一、If...Else语句1. 基本语法2. 带有 elseif 的语法3. 示例示例 1&#xff1a;基本 if...else 结构示例 2&#xff1a;使用 elseif示例 3&#xff1a;嵌套 if...else 结构 4. 注意事项 二、switch语句1. 基本语法2. 示例示例 1&#xff1a;基本 switch 结构示例 2&am…...

Ubuntu部署BOA服务器

BOA服务器概述 BOA是一款非常小巧的Web服务器&#xff0c;源代码开放、性能优秀、支持CGI通用网关接口技术&#xff0c;特别适合用在嵌入式系统中。 BOA服务器主要功能是在互联嵌入式设备之间进行信息交互&#xff0c;达到通用网络对嵌入式设备进行监控&#xff0c;并将反馈信…...

安卓Glide加载失败时点击按钮重新加载图片

需求 假设此时已经用load指定一个url: String&#xff0c;又用into指定了一个img: ImageView开始加载&#xff0c;但是网络突然中断&#xff0c;导致图片加载失败。在这种情况下&#xff0c;想要通过点击一个Button重新加载。 Glide.with(context).load(url).placeholder(loa…...

linux下python服务定时(自)启动

AI应用开发相关目录 本专栏包括AI应用开发相关内容分享&#xff0c;包括不限于AI算法部署实施细节、AI应用后端分析服务相关概念及开发技巧、AI应用后端应用服务相关概念及开发技巧、AI应用前端实现路径及开发技巧 适用于具备一定算法及Python使用基础的人群 AI应用开发流程概…...

awk命令进阶操作(二)

awk模块 awk模块awk的BEGIN模块和END模块BEGIN模块BEGIN 常见错误END模块END模块 常见错误 案例计算1~100的累加和统计系统中有多少用户的shell类型是/bin/bash awk模块 awk的BEGIN模块和END模块 格式 awk BEGIN{}{}END{} 文件名BEGIN模块 用于定义一个动作&#xff0c;用{…...

【洛谷 P8695】[蓝桥杯 2019 国 AC] 轨道炮 题解(映射+模拟+暴力枚举+桶排序)

[蓝桥杯 2019 国 AC] 轨道炮 题目描述 小明在玩一款战争游戏。地图上一共有 N N N 个敌方单位&#xff0c;可以看作 2D 平面上的点。其中第 i i i 个单位在 0 0 0 时刻的位置是 ( X i , Y i ) (X_i, Y_i) (Xi​,Yi​)&#xff0c;方向是 D i D_i Di​ (上下左右之一, 用…...

高阶DS---AVL树详解(每步配图)

目录 前言&#xff1a; AVL树的概念: AVL树节点的定义&#xff1a; AVL树的插入&#xff08;重点&#xff09; AVL树的旋转&#xff1a; &#xff08;1&#xff09;新节点插入较高左子树的左侧---右单旋 &#xff08;2&#xff09;新节点插入较高右子树的右侧---左单旋 …...

c++前言

目录 1. 什么是 C 2. C 发展史 3. C 的重要性 4. 如何学习 C 5. 关于本门课程 1. 什么是C C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的 程序&#xff0c;需要高度的抽象和建模时&#xff0c; C 语言则不合适…...

2024年泰迪杯数据挖掘B题详细思路代码文章教程

目前b题已全部更新包含详细的代码模型和文章&#xff0c;本文也给出了结果展示和使用模型说明。 同时文章最下方包含详细的视频教学获取方式&#xff0c;手把手保姆级&#xff0c;模型高精度&#xff0c;结果有保障&#xff01; 分析&#xff1a; 本题待解决问题 目标&#…...

练习 21 Web [GXYCTF2019]BabySQli

SQL联合查询&#xff0c;注意有源码看源码&#xff0c;Base64以及32的区别&#xff0c;MD5碰撞 打开后有登录框&#xff0c;先随意登录尝试 只有输入admin才是返回wrong pass&#xff01; 其他返回wrong user 所以用户名字段一定要输入admin 养成好习惯&#xff0c;先查看源码…...

【并发编程】CountDownLatch

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;并发编程 ⛺️稳中求进&#xff0c;晒太阳 CountDownLatch 概念 CountDownLatch可以使一个获多个线程等待其他线程各自执行完毕后再执行。 CountDownLatch 定义了一个计数器&#xff0c;…...

2024-HW --->SSRF

这不是马上准备就要护网了嘛&#xff0c;如火如荼的报名ing&#xff01;&#xff01;&#xff01;那么小编就来查缺补漏一下以前的web漏洞&#xff0c;也顺便去收录一波poc&#xff01;&#xff01;&#xff01;&#xff01; 今天讲的主人公呢就是SSRF&#xff0c;以前学的时候…...

该主机与 Cloudera Manager Server 失去联系的时间过长。 该主机未与 Host Monitor 建立联系

该主机与 Cloudera Manager Server 失去联系的时间过长。 该主机未与 Host Monitor 建立联系 这个去集群主机cm界面上看会出现这个错误 排查思路&#xff1a; 一般比较常见的原因可能是出问题的主机和集群主节点的时间对应不上了。还有就是cm agent服务出现问题了 去该主机的…...

CVPR 2025 MIMO: 支持视觉指代和像素grounding 的医学视觉语言模型

CVPR 2025 | MIMO&#xff1a;支持视觉指代和像素对齐的医学视觉语言模型 论文信息 标题&#xff1a;MIMO: A medical vision language model with visual referring multimodal input and pixel grounding multimodal output作者&#xff1a;Yanyuan Chen, Dexuan Xu, Yu Hu…...

React Native 导航系统实战(React Navigation)

导航系统实战&#xff08;React Navigation&#xff09; React Navigation 是 React Native 应用中最常用的导航库之一&#xff0c;它提供了多种导航模式&#xff0c;如堆栈导航&#xff08;Stack Navigator&#xff09;、标签导航&#xff08;Tab Navigator&#xff09;和抽屉…...

Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例

使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件&#xff0c;常用于在两个集合之间进行数据转移&#xff0c;如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model&#xff1a;绑定右侧列表的值&…...

HTML前端开发:JavaScript 常用事件详解

作为前端开发的核心&#xff0c;JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例&#xff1a; 1. onclick - 点击事件 当元素被单击时触发&#xff08;左键点击&#xff09; button.onclick function() {alert("按钮被点击了&#xff01;&…...

Unit 1 深度强化学习简介

Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库&#xff0c;例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体&#xff0c;比如 SnowballFight、Huggy the Do…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

探索Selenium:自动化测试的神奇钥匙

目录 一、Selenium 是什么1.1 定义与概念1.2 发展历程1.3 功能概述 二、Selenium 工作原理剖析2.1 架构组成2.2 工作流程2.3 通信机制 三、Selenium 的优势3.1 跨浏览器与平台支持3.2 丰富的语言支持3.3 强大的社区支持 四、Selenium 的应用场景4.1 Web 应用自动化测试4.2 数据…...

论文阅读:LLM4Drive: A Survey of Large Language Models for Autonomous Driving

地址&#xff1a;LLM4Drive: A Survey of Large Language Models for Autonomous Driving 摘要翻译 自动驾驶技术作为推动交通和城市出行变革的催化剂&#xff0c;正从基于规则的系统向数据驱动策略转变。传统的模块化系统受限于级联模块间的累积误差和缺乏灵活性的预设规则。…...

Kubernetes 节点自动伸缩(Cluster Autoscaler)原理与实践

在 Kubernetes 集群中&#xff0c;如何在保障应用高可用的同时有效地管理资源&#xff0c;一直是运维人员和开发者关注的重点。随着微服务架构的普及&#xff0c;集群内各个服务的负载波动日趋明显&#xff0c;传统的手动扩缩容方式已无法满足实时性和弹性需求。 Cluster Auto…...

uni-app学习笔记三十五--扩展组件的安装和使用

由于内置组件不能满足日常开发需要&#xff0c;uniapp官方也提供了众多的扩展组件供我们使用。由于不是内置组件&#xff0c;需要安装才能使用。 一、安装扩展插件 安装方法&#xff1a; 1.访问uniapp官方文档组件部分&#xff1a;组件使用的入门教程 | uni-app官网 点击左侧…...