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

C++线程池实现

C++线程池实现

  • 知识点补充
  • 为什么要实现线程池
  • 线程池的实现过程

知识点补充

在C++11中引入了对线程库的支持,接下来我们介绍一下跟线程相关的一些知识点:

线程对象的构造方式

在C++11中主要提供的三种线程的构造方式:无参构造带参构造调用移动构造函数

#include <iostream>
#include <thread>void Func1(int a)
{std::cout << "void Func1(int a)" << std::endl;
}int main()
{std::thread t1; // 无参进行构造std::thread t2(Func1, 1); // 带参进行构造// t1 = t2; /*t2线程赋给t1线程,这样是不可取的因为在线程库底层实现过程中是将拷贝构造函数设置成delete的*/t1 = std::move(t2); // 使用移动构造函数进行构造std::cout << t1.get_id() << std::endl;/*注意:此时的t2线程是获取不到对应的id的因为他的状态已经转移给其他的线程对象了同时,也不可去进行join了,会系统报错*/// std::cout << t2.get_id() << std::endl;// std::cout << t2.joinable() << std::endl;  == 0t1.join();// t2.join();return 0;
}

在使用的过程中我们需要注意几点:

  • thread类是防拷贝的,不允许拷贝构造和拷贝赋值,但是可以移动构造和移动赋值,可以将一个线程对象关联线程的状态转移给其他线程对象,并且转移期间不影响线程的执行。
  • 在移动构造或者移动赋值以后,此时一个线程对象关联线程的状态转移给其他线程对象,就不能在进行join了;
  • detach调用之后,目标线程就成为了守护线程,驻留后台运行,与之关联的std::thread对象
    失去对目标线程的关联,无法再通过std::thread对象取得该线程的控制权。当线程主函数执
    行完之后,线程就结束了,运行时库负责清理与该线程相关的资源,如下所示:
void Func1(int a)
{std::cout << "void Func1(int a)" << std::endl;
}void Func2(int a)
{std::cout << "void Func2(int a)" << std::endl;
}int main()
{std::cout << "main start >>>>>>>>>>>>>>>" << std::endl;std::thread t1(Func1, 1);t1.join();std::thread t2(Func2, 2);// std::this_thread::sleep_for(std::chrono::milliseconds(5));/*如果在这儿不进行睡眠的话,会发现当前线程就不再被控制,他的打印也就根据他执行的时机和次数而定*/t2.detach();std::cout << "main end >>>>>>>>>>>>>>>" << std::endl;return 0;
}

当然,我们对应的线程传入的对参数也可以是类函数的,如下所示:

class A
{
public:static void Func4(int a){std::cout << "void Func4(int a)->" << a << std::endl;}
};int main()
{A* newA = new A();std::thread t1(A::Func4, 10);t1.join();return 0;
}

对于互斥量以及条件变量可以参考之前的文章:C++11之线程库

std::aysncstd::future

std::aysnc表示异步运行某个任务函数,而std::future异步指向某个任务,然后通过 future 特性去获取任务函数的返回结果。

std::future期待一个返回,从一个异步调用的角度来说,future 更像是执行函数的返回值,C++标准库使用std::future为一次性事件建模,如果一个事件需要等待特定的一次性事件,那么这线程可以获取一个 future 对象来代表这个事件。

异步调用往往不知道何时返回,但是如果异步调用的过程需要同步,或者说后一个异步调用需要使前一个异步调用的结果。这个时候就要用到 future。

线程可以周期性的在这个 future 上等待一小段时间,检查future是否已经ready,如果没有,该线程可以先去做另一个任务,一旦 future 就绪,该 future 就无法复位(无法再次使用这个 future 等待这个事件),所以 future 代表的是一次性事件。

std::async返回一个std::future对象,而不是给你一个确定的值(所以当你不需要立刻使用此值的时才需要用到这个机制)。当你需要使用这个值的时候,对 future 使用get(),线程就会阻塞直到 future 绪,然后返回该值。

我们用一下一段代码来理解一下:

int find_result1(int a)
{std::this_thread::sleep_for(std::chrono::seconds(5));std::cout << "thread id find_result1: " << std::this_thread::get_id() << std::endl;std::cout << "void find_result1(int a)->" << a << std::endl;return a;
}int find_result2(int a, int b)
{std::cout << "thread id find_result2: " << std::this_thread::get_id() << std::endl;std::cout << "void find_result1(int a)->" << a + b << std::endl;return a + b;
}void do_other_things()
{std::cout << "thread id do_other_things: " << std::this_thread::get_id() << std::endl;std::cout << "do_other_things !!!" << std::endl;
}int main()
{// std::future<int> result1 = std::async(find_result1, 1);std::future<decltype(find_result1(0))> result1 = std::async(find_result1, 1);// 两种写法,更推荐使用第二种do_other_things();std::cout << "result1: " << result1.get() << std::endl;return 0;
}

std::future是 C++11 标准库(并发支持库)中的一个模板类,它表示一个异步操作的结果。当我们在多线程编程中使用异步任务时,std::future可以帮助我们在需要的时候获取任务的执行结果。

作用

  • 异步操作的结果获取std::future提供了一种机制,允许我们在多线程环境中安全地获取异步操作的结果。
  • 隐藏异步操作的细节std::future将异步操作的结果封装起来,使程序员无需关注线程同步和通信的具体实现细节。
  • 线程同步:通过阻塞等待异步操作完成,std::future可以确保我们在继续执行其他操作之前,已经获取了所需的结果。
  • 异常处理std::future可以捕获异步操作中抛出的异常,并在获取结果时重新抛出,也可以在主线程中对其进行处理,从而使得异常处理更加简单。
  • 提高性能std::future使得我们能够更好地利用多核处理器的性能,通过并行执行任务来提高程序的执行效率。

当我们需要在后台执行一些耗时操作时,如文件读写、网络请求或计算密集型任务,std::future可以用来表示这些异步任务的结果。通过将任务与主线程分离,我们可以实现任务的并行处理,从而提高程序的执行效率。或者是在多线程编程中,我们可能需要等待某些任务完成后才能继续执行其他操作。通过使用std::future,我们可以实现线程之间的同步,确保任务完成后再获取结果并继续执行后续操作。

std::promise

std::promise提供了一种设置值的方式,它可以在这之后通过相关联的std::future对象进行读取。换种
说法,之前已经说过std::future可以读取一个异步函数的返回值了,那么这个std::promise就提供一种
方式手动让 future 就绪。

看下面这段代码:

void print(std::promise<std::string>& p)
{std::this_thread::sleep_for(std::chrono::seconds(5));std::cout << "thread id print: " << std::this_thread::get_id() << std::endl;// 将结果设置到promise当中去p.set_value("There is  the result which you want !!!");
}void do_other_things()
{std::cout << "thread id do_other_things: " << std::this_thread::get_id() << std::endl;std::cout << "do_other_things !!!" << std::endl;
}int main()
{std::promise<std::string> promise;std::future<std::string> result = promise.get_future();// 创建一个线程执行当前任务std::thread t(print, std::ref(promise));// 执行其他任务do_other_things();std::cout << result.get() << std::endl;t.join();return 0;
}

由此可以看出在 promise 创建好的时候 future 也已经创建好了线程在创建 promise 的同时会获得一个future,然后将 promise 传递给设置他的线程,当前线程则持有 future,以便随时检查是否可以取值。

std::packaged_task

如果说std::asyncstd::future还是分开看的关系的话,那么std::packaged_task就是将任务和 future 绑定在一起的模板,是一种封装对任务的封装。

可以通过std::packaged_task对象获取任务相关联的 future,调用get_future()方法可以获得
std::packaged_task对象绑定的函数的返回值类型的future。std::packaged_task的模板参数是函数签
名。例如:int add(int a, intb)的函数签名就是int(int, int)

int add(int a, int b, int c)
{std::this_thread::sleep_for(std::chrono::seconds(5));std::cout << "call add\n";return a + b + c;
}void do_other_things()
{std::cout << "thread id do_other_things: " << std::this_thread::get_id() << std::endl;std::cout << "do_other_things !!!" << std::endl;
}int main()
{std::packaged_task<int(int, int, int)> task(add); // 封装任务// 执行其他任务do_other_things();std::future<int> result = task.get_future();// task(1, 2, 3); // 必须让任务执行起来,否则在get()获取future值时会一直阻塞std::cout << "result: " << result.get() << std::endl;return 0;
}

为什么要实现线程池

线程池(Thread Pool)是一种并发编程中常用的技术,用于管理和重用线程。它由线程池管理器、工作队列和线程池线程组成。

线程池的基本概念是,在应用程序启动时创建一定数量的线程,并将它们保存在线程池中。当需要执行任务时,从线程池中获取一个空闲的线程,将任务分配给该线程执行。当任务执行完毕后,线程将返回到线程池,可以被其他任务复用。

我们试想,如果某类任务的处理时间特别长,但是当前线程需要执行的任务也很多,那么就会导致没有执行完当前任务就无法执行其他任务的现象,极大的降低了工作效率,此时,使用线程池就可以解决掉这个问题,我们可以让其他线程来异步执行这个任务,这样也就极大地提高了效率。

而且,大量的线程的创建跟销毁会影响系统的性能与资源利用率的,程池的设计思想是为了避免频繁地创建和销毁线程的开销,以及控制并发执行的线程数量,从而提高系统的性能和资源利用率。

线程池的实现过程

以下就是线程池的实现代码:

threadPool.h

#ifndef __M_THREADPOOL_H__
#define __M_THREADPOOL_H__#include <thread>
#include <iostream>
#include <mutex>
#include <queue>
#include <condition_variable>
#include <atomic>
#include <functional>
#include <future>
#include <memory>
#include <string>const int NUM = 5; // 默认线程数量5个// template <class T>
class ThreadPool
{
protected:struct Task{Task(){ }std::function<void()>   _func;};typedef std::shared_ptr<Task> taskPtr;public:ThreadPool();virtual ~ThreadPool();// 用线程池启用任务// 返回任务的future对象, 可以通过这个对象来获取返回值template <class F, class... Args>auto exec(F &&f, Args &&...args) -> std::future<decltype(f(args...))>{return exec1(f, args...);}template <class F, class... Args>auto exec1(F &&f, Args &&...args) -> std::future<decltype(f(args...))>{// 定义返回值类型using RetType = decltype(f(args...)); // 推导返回值// 封装任务auto task = std::make_shared<std::packaged_task<RetType()>>(std::bind(std::forward<F>(f), std::forward<Args>(args)...));// 封装任务指针taskPtr tPtr = std::make_shared<Task>();// 具体执行函数tPtr->_func = [task](){(*task)();};// 插入任务std::unique_lock<std::mutex> lock(_mutex);_taskqueue.push(tPtr);_cond.notify_one();return task->get_future();}// 线程池的初始化bool init(const int num = NUM);// 停止线程池void stop();// 启动所有线程bool start();// 获取线程数量size_t getThreadNumber();// 获取任务数量size_t getTaskNumder();// 获取任务bool getTask(taskPtr &task);// 执行当前线程处理任务void run();// 等在所有任务执行完毕bool waitforAllDone();private:int _num;                            // 线程数量std::queue<taskPtr> _taskqueue;      // 任务队列std::mutex _mutex;                   // 互斥锁std::condition_variable _cond;       // 条件变量bool _stop;                          // 停止标志std::vector<std::thread *> _threads; // 线程组std::atomic<int> count{0};           // 标志
};#endif

threadPool.cpp

#include "threadPool.h"// 构造函数,初始化线程数量以及线程池状态
ThreadPool::ThreadPool() : _num(NUM), _stop(false)
{
}// 析构函数
ThreadPool::~ThreadPool()
{stop();
}// 初始化线程数量
bool ThreadPool::init(int num)
{std::unique_lock<std::mutex> lock(_mutex);if (!_taskqueue.empty()){std::cout << "thread already init !!!" << std::endl;return false;}_num = num;std::cout << "thread init succ !!!" << std::endl;return true;
}// 初始化线程池
bool ThreadPool::start()
{std::unique_lock<std::mutex> lock(_mutex);// 如果当前线程池的数量不为空,就不需要添加线程进去if (!_threads.empty()){std::cout << "threadpool not empty !!!\n";return false;}for (size_t i = 0; i < _num; i++){_threads.push_back(new std::thread(&ThreadPool::run, this));}std::cout << "threadpool init succ !!!\n";return true;
}// 停止线程池
void ThreadPool::stop()
{{std::unique_lock<std::mutex> lock(_mutex);_stop = true;// 唤醒所有线程_cond.notify_all();}for (size_t i = 0; i < _threads.size(); i++){if (_threads[i]->joinable()){_threads[i]->join();}delete _threads[i];_threads[i] = nullptr;}std::unique_lock<std::mutex> lock(_mutex);_threads.clear();
}// 执行当前线程的处理任务
void ThreadPool::run()
{while (!_stop){taskPtr task;auto ret = getTask(task);if (ret){// 证明当前正在执行该任务++count;task->_func();}}--count;// 所有任务执行完毕std::unique_lock<std::mutex> lock(_mutex);if (count == 0 && _taskqueue.empty()){// 为了waitforAllDone_cond.notify_all();}
}// 从任务队列中获取任务
bool ThreadPool::getTask(taskPtr &task)
{std::unique_lock<std::mutex> lock(_mutex);if (_taskqueue.empty()){_cond.wait(lock, [this](){ return _stop || !_taskqueue.empty(); });}if (_stop){return false;}if (!_taskqueue.empty()){task = std::move(_taskqueue.front());_taskqueue.pop();return true;}return false;
}// 获取线程数量
size_t ThreadPool::getThreadNumber()
{std::unique_lock<std::mutex> lock(_mutex);return _threads.size();
}// 获取任务数量
size_t ThreadPool::getTaskNumder()
{std::unique_lock<std::mutex> lock(_mutex);return _taskqueue.size();
}// 等待所有任务执行完毕
bool ThreadPool::waitforAllDone()
{std::unique_lock<std::mutex> lock(_mutex);if (_taskqueue.empty()){return true;}return _cond.wait_for(lock, std::chrono::milliseconds(100), [this] { return _taskqueue.empty(); });
}

main.cpp

#include <iostream>
#include <chrono>
#include "threadPool.cpp"void func1()
{// std::this_thread::sleep_for(std::chrono::seconds(5));std::cout << "void func1()" << std::endl;
}int func2(int a)
{// std::this_thread::sleep_for(std::chrono::seconds(3));std::cout << "void func2()->" << a << std::endl;return a;
}void func3(int a, std::string str)
{std::cout << "void func3()->" << a << ":" << str << std::endl;
}int main()
{ThreadPool thread_pool;thread_pool.init(3);thread_pool.start();// thread_pool.exec(func1);// thread_pool.exec(func2, 10);// thread_pool.exec(func3, 10, "hello");std::future<decltype(func2(1))> result = thread_pool.exec(func2, 1);std::cout << "result: " << result.get() << std::endl;thread_pool.waitforAllDone();thread_pool.stop();return 0;
}

相关文章:

C++线程池实现

C线程池实现 知识点补充为什么要实现线程池线程池的实现过程 知识点补充 在C11中引入了对线程库的支持&#xff0c;接下来我们介绍一下跟线程相关的一些知识点&#xff1a; 线程对象的构造方式 在C11中主要提供的三种线程的构造方式&#xff1a;无参构造、带参构造和调用移动构…...

#Redis缓存篇#(七)分布式缓存

目录 一 单节点Redis 1 单节点的问题 二 分布式缓存 1 Redis持久化 &#xff08;1 RDB持久化 &#xff08;2 AOF持久化 2 Redis主从集群 &#xff08;1 搭建主从架构 &#xff08;2 主从数据同步原理 3 Redis哨兵 &#xff08;1 哨兵的作用和原理 &#xff08;2 搭…...

【VSCode】安装与 ssh 免密登录

【VSCode】安装与 ssh 免密登录 下载SSH 登录设置免密登录关闭远程连接删除ssh连接&#xff08;慎用&#xff01;&#xff01;&#xff01;删除了建立的连接就没有了&#xff01;&#xff01;&#xff09; 下载 https://code.visualstudio.com/docs/?dvwin64user 选择安装路径…...

【Python解决八皇后问题】回溯算法与优化策略全解析

目录 🌟 前言🏗️ 技术背景与价值🩹 当前技术痛点🛠️ 解决方案概述👥 目标读者说明🧠 一、技术原理剖析📊 核心概念图解💡 核心作用讲解🔧 关键技术模块说明⚖️ 技术选型对比🛠️ 二、实战演示⚙️ 环境配置要求💻 核心代码实现基础回溯实现位运算优化…...

判断一个元素是否在可视区域

判断元素是否在可视区域的方法 方法一:offsetTop 和 scrollTop 通过计算元素的 offsetTop 和容器的 scrollTop 来判断元素是否位于视口内。这种方法适用于简单的垂直滚动场景。 优点: 实现简单,性能较好。缺点: 不支持复杂的布局结构(如嵌套滚动),无法处理水平方向上的可…...

作物遗传与种质创新利用全国重点实验室-随笔10

作物遗传与种质创新利用全国重点实验室依托于南京农业大学&#xff0c;2022年11月完成国家重点实验室重组工作&#xff0c;由原名称“作物遗传与种质创新国家重点实验室”正式更名为“作物遗传与种质创新利用全国重点实验室”。 实验室面向国家粮食安全和农业高质量发展的重大战…...

分布式电源的配电网无功优化

分布式电源(Distributed Generation, DG)的大规模接入配电网,改变了传统单向潮流模式,导致电压波动、功率因数降低、网损增加等问题,无功优化成为保障配电网安全、经济、高效运行的关键技术。 1. 核心目标 电压稳定性:抑制DG并网点(PCC)及敏感节点的电压越限(如超过5%…...

游戏引擎学习第301天:使用精灵边界进行排序

回顾并为今天的内容做准备 昨天&#xff0c;我们解决了一些关于排序的问题&#xff0c;这对我们清理长期存在的Z轴排序问题很有帮助。这个问题我们一直想在开始常规游戏代码之前解决。虽然不确定是否完全解决了问题&#xff0c;但我们提出了一个看起来合理的排序标准。 有两点…...

网络框架二次封装:基于Kotlin的高扩展性网络请求框架完整实现

完整目录结构 1. 架构设计1.1 分层架构1.2 核心组件1.3 接口关系图2. 基础配置实现2.1 NetworkConfig完整代码2.2 CacheConfig完整代码3. 核心网络客户端3.1 SmartHttpClient完整实现3.2 单例管理3.3 服务创建与执行4. DSL请求构建器4.1 NetworkRequest完整实现4.2 生命周期绑…...

高噪声下扩展边缘检测算子对检测边缘的影响

目录 一、常见的边缘检测算子 二、扩展边缘检测算子对检测边缘的影响 三、结论 一、常见的边缘检测算子 Sobel 算子: Prewitt算子;...

Linux 内核音视频架构(V4L2 )介绍

一.概述 Linux 内核中的 V4L2&#xff08;Video for Linux Two&#xff09;框架 是用于管理音视频设备&#xff08;如摄像头、电视调谐器、视频采集卡等&#xff09;的核心子系统。 它提供了一套统一的接口&#xff0c;使得用户空间应用程序能够方便地访问和控制硬件设备&…...

专业 YouTube SEO 方案:打造高排名视频的关键步骤

YouTube 是全球订阅量最高的社交媒体平台之一。YouTube 为发布创意视频内容和针对特定受众开展营销活动提供了无限可能&#xff0c;是任何品牌内容营销策略的重要组成部分。 但是&#xff0c;为了发展您的 YouTube 频道并消除噪音&#xff0c;优化您的视频内容以便可以在搜索结…...

基于STM32的智能台灯_自动亮度_久坐提醒仿真设计(Proteus仿真+程序设计+设计报告+讲解视频)

这里写目录标题 1.主要功能2.仿真设计3.程序设计4.设计报告5.下载链接 基于STM32的智能台灯_自动亮度_久坐提醒仿真设计 (Proteus仿真程序设计设计报告讲解视频&#xff09; 仿真图Proteus 8.9 程序编译器&#xff1a;keil 5 编程语言&#xff1a;C语言 设计编号&#xff1…...

labview硬件部分——压力测量

0kg的电压需要我们手动输入&#xff01;在不放东西的时候的电压&#xff0c;先运行一次程序&#xff0c;将其记录后写到程序中的0kg输入按键即可。 整体的程序&#xff1a;...

Mysql索引实战1

对于上面这两种 name>‘a’ 和 name>‘zzz’ 的执行结果&#xff0c;mysql最终是否选择走索引或者一张表涉及多个索引&#xff0c;mysql最终如何选择索引&#xff0c;我们可以用trace工具来一查究竟&#xff0c;开启trace工具会影响mysql性能&#xff0c;所以只能临时分析…...

在实际网络部署中,静态路由的优先级通常高于RIP

是的&#xff0c;在实际网络部署中&#xff0c;静态路由的优先级通常高于RIP&#xff0c;尤其是在中小型网络或对可控性要求高的场景中。以下是关键原因和典型应用场景分析&#xff1a; 1. 为何静态路由比RIP更受青睐&#xff1f; (1) 简单性与可靠性 静态路由&#xff1a; 手…...

Linux系统编程-DAY02

一、标准io 1.写文件 fgets函数中判断有多少行&#xff0c;且判断最后一个是不是终止符 if( buf[strlen(buf) - 1] \n ) 2. wc命令行&#xff1a;字符统计 wc -l 文件名 行数 文件名 3. write 用于操作二进制的文件&#xff08;文办文件和图片文件也可以…...

【C++ 真题】P5736 【深基7.例2】质数筛

P5736 【深基7.例2】质数筛 题目描述 输入 n n n 个不大于 10 5 10^5 105 的正整数。要求全部储存在数组中&#xff0c;去除掉不是质数的数字&#xff0c;依次输出剩余的质数。 输入格式 第一行输入一个正整数 n n n&#xff0c;表示整数个数。 第二行输入 n n n 个正…...

自制操作系统day6(GDTR、段描述符、PIC、实模式和保护模式、16位到32位切换、中断处理程序、idt的设定、EFLAG寄存器)(ai辅助整理)

day6 分割源文件&#xff08;harib03a&#xff09; 优点 按照处理内容进行分类&#xff0c;如果分得好的话&#xff0c;将来进行修改时&#xff0c;容易找到地方。如果Makefile写得好&#xff0c;只需要编译修改过的文件&#xff0c;就可以提高make的速度。单个源文件都不长。…...

大模型评测与可解释性

随着大模型在各个领域展现出惊人的能力,我们对其性能的评估和对其决策过程的理解变得尤为重要。一个模型即使在基准测试中表现出色,也可能在实际应用中遇到意想不到的问题。同时,由于大模型的复杂性,它们常常被视为“黑箱”,这给其在关键领域的应用带来了挑战。 本章将深…...

【TTS回顾】StyleTTS 深度剖析:TTS+风格迁移

写在前面 这篇博客我们回顾一下StyleTTS,当时的背景是,文本转语音(TTS)技术,早已不再满足于仅仅将文字转化为可听的语音。行业需要的是“真人TTS”,AI 不仅能“说得清楚”,更能“说得生动”、“说得有感情”,甚至能模仿特定人物的说话风格。富有表现力的语音合成,即能…...

GStreamer (四)交叉编译

交叉编译 下载链接库交叉编译1、下载Gstreamer &#xff08;方式二 &#xff09;&#xff0c;进入到编译目录2、在gst-build目录下创建交叉编译配置文件cross_file.txt3、修改meson_options.txt中libmount选项为false&#xff0c;否则编译前需要先编译libmount。4、在gst-build…...

电路设计基础

只有当电容两端的电压等于0伏的时候&#xff0c;就是这一点的电压和这一点电压之间没有压差的时候&#xff0c;我门才可以把电容当成是一根导线&#xff0c;如果当我电容比如说它己经充到有一个1伏的电压了&#xff0c;这个时候我们是不可以把电容当成是导线的&#xff0c;所以…...

C语言——函数递归与迭代

&#xff08;1&#xff09;递归的例子&#xff1a; 顺序打印一个整数&#xff0c;打印整数的每一位。 例如&#xff1a; input:1234 output:1 2 3 4 input:520 output:5 2 0 我们可能会想到用这种方法&#xff1a;&#xff08;但是运行之后&#xff0c;我们发现结果是事…...

详解 C# 中基于发布-订阅模式的 Messenger 消息传递机制:Messenger.Default.Send/Register

&#x1f9d1; 博主简介&#xff1a;CSDN博客专家、CSDN平台优质创作者&#xff0c;高级开发工程师&#xff0c;数学专业&#xff0c;10年以上C/C, C#, Java等多种编程语言开发经验&#xff0c;拥有高级工程师证书&#xff1b;擅长C/C、C#等开发语言&#xff0c;熟悉Java常用开…...

8 种快速易用的Python Matplotlib数据可视化方法

你是否曾经面对一堆复杂的数据&#xff0c;却不知道如何让它们变得直观易懂&#xff1f;别慌&#xff0c;Python 的 Matplotlib 库是你数据可视化的最佳伙伴&#xff01;它简单易用、功能强大&#xff0c;能将枯燥的数字变成引人入胜的图表。无论是学生、数据分析师还是程序员&…...

嵌入式开发学习日志(linux系统编程--文件读写函数(2))Day25

一、linux操作命令 【wc】&#xff1a;指定字符统计&#xff1b; 【file 文件名】&#xff1a;可以查看文件的类型&#xff1b; 二、写入函数【fwrite】————可写入二进制文件 形式&#xff1a; size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE…...

离线服务器Python环境配置指南

离线服务器Python环境配置指南&#xff1a;避坑与实战 0. 场景分析&#xff1a;当服务器与世隔绝时 典型困境&#xff1a; 无法访问国际网络&#xff08;如PyPI、Conda官方源&#xff09;服务器处于内网隔离环境安全策略限制在线安装 解决方案矩阵&#xff1a; 方法适用场…...

Java线程池调优与实践经验

在Java面试中&#xff0c;线程池调优是一个常见且重要的考察点&#xff0c;尤其是当涉及Spring生态时&#xff0c;ThreadPoolTaskExecutor的使用经验通常会被深入追问。以下是针对该问题的结构化回答&#xff0c;结合原理、实践和调优经验&#xff1a; 1. 线程池调优的核心参数…...

Python 包管理工具核心指令uvx解析

uvx 是 Python 包管理工具 uv 的重要组成部分&#xff0c;主要用于在隔离环境中快速运行 Python 命令行工具或脚本&#xff0c;无需永久安装工具包。以下是其核心功能和使用场景的详细解析&#xff1a; 一、uvx 的定位与核心功能 工具执行器的角色 uvx 是 uv tool run 的别名&a…...

力扣-三数之和

1.题目描述 2.题目链接 LCR 007. 三数之和 - 力扣&#xff08;LeetCode&#xff09; 3.题目代码 import java.util.*; class Solution {public List<List<Integer>> threeSum(int[] nums) {Arrays.sort(nums);int tempnums.length-1;Set<List<Integer>…...

【AI模型学习】ESM2

文章目录 1. 版本2. 开始2.1 安装2.2 使用预训练模型2.2.1 代码2.2.2 讲解 2.2 结构预测 3. 任务类型总结1. 蛋白质结构预测&#xff08;ESMfold&#xff09;2. 特征嵌入提取&#xff08;esm-extract&#xff09;3. 零镜头变体预测&#xff08;ESM-1v/ESM-2&#xff09;4. 逆向…...

c++11特性——可变参数模板及emplace系列接口

文章目录 可变参数模板基本语法和使用sizeof...运算符 从语法角度理解可变参数模板包扩展通过编译时递归解析参数包直接对解析行为展开 emplace系列接口举例讲解emplace_back的实现 可变参数模板 可变参数模板是c11新特性中极其重要的一节。前文我们提到过&#xff0c;c11中对…...

深入理解 Pre-LayerNorm :让 Transformer 训练更稳

摘要 在超深 Transformer 与大语言模型&#xff08;LLM&#xff09;时代&#xff0c;归一化策略直接决定了模型能否稳定收敛、推理性能能否最大化。把归一化层从 “残差之后” 挪到 “子层之前”&#xff08;Pre-LayerNorm&#xff0c;Pre-LN&#xff09;&#xff0c;再将传统…...

vue3:十三、分类管理-表格--分页功能

一、实现效果 实现分页功能,并且可对分页功能和搜索框功能能动态显示 1、显示分页 2、分页功能和搜索栏隐藏 二、基础搭建 1、官网参考 Pagination 分页 | Element Plus 使用分页的附加功能 2、表格中底部写入分页 (1)样式class 在全局js中写入顶部外边距样式margin-t…...

工商总局可视化模版-Echarts的纯HTML源码

概述 基于ECharts的工商总局数据可视化HTML模版&#xff0c;帮助开发者快速搭建专业级工商广告数据展示平台。这款模版设计规范&#xff0c;功能完善&#xff0c;适合各类工商监管场景使用。 主要内容 本套模版采用现代化设计风格&#xff0c;主要包含以下核心功能模块&…...

8.2 线性变换的矩阵

一、线性变换的矩阵 本节将对每个线性变换 T T T 都指定一个矩阵 A A A. 对于一般的列向量&#xff0c;输入 v \boldsymbol v v 在空间 V R n \pmb{\textrm V}\pmb{\textrm R}^n VRn 中&#xff0c;输出 T ( v ) T(\boldsymbol v) T(v) 在空间 W R m \textrm{\pmb W}\…...

工业路由器WiFi6+5G的作用与使用指南,和普通路由器对比

工业路由器的技术优势 在现代工业环境中&#xff0c;网络连接的可靠性与效率直接影响生产效率和数据处理能力。WiFi 6&#xff08;即802.11ax&#xff09;和5G技术的结合&#xff0c;为工业路由器注入了强大的性能&#xff0c;使其成为智能制造、物联网和边缘计算的理想选择。…...

Nginx核心服务

一&#xff0e;正向代理 正向代理&#xff08;Forward Proxy&#xff09;‌是一种位于客户端和原始服务器之间的代理服务器&#xff0c;其主要作用是将客户端的请求转发给目标服务器&#xff0c;并将响应返回给客户端 Nginx 的 正向代理 充当客户端的“中间人”&#xff0c;代…...

条件随机场 (CRF) 原理及其在语义分割中的应用

条件随机场 (CRF) 原理及其在语义分割中的应用 一、条件随机场的原理 条件随机场 (Conditional Random Fields, CRF) 是一种判别式概率无向图模型。它用于在给定观测序列 (如图像中的像素) 的条件下&#xff0c;对另一组序列 (如像素的语义标签) 进行建模和预测。 与生成式模…...

2025年Y2大型游乐设施操作证备考练习题

Y2 大型游乐设施操作证备考练习题 单选题 1、《游乐设施安全技术监察规程&#xff08;试行&#xff09;》规定&#xff1a;对操作控制人员无法观察到游乐设施的运行情况&#xff0c;在可能发生危险的地方应&#xff08; &#xff09;&#xff0c;或者采取其他必要的安全措施。…...

L53.【LeetCode题解】二分法习题集2

目录 1.162. 寻找峰值 分析 代码 提交结果 2.153. 寻找旋转排序数组中的最小值 分析 图像的增长趋势可以分这样几类 逐个击破 比较明显的 先增后减再增 用二段性给出left和right的更新算法 代码 提交结果 其他做法 提交结果 3.LCR 173. 点名(同剑指offer 53:0~…...

趣味编程:抽象图(椭圆组成)

概述&#xff1a;本篇博客主要讲解由椭圆图案组合而成的抽象图形。 1.效果展示 该程序的实际运行是一个动态的效果&#xff0c;因此实际运行相较于博客图片更加灵动。 2.源码展示 // 程序名称&#xff1a;椭圆组合而成的抽象图案// #include <graphics.h> #include <…...

RPA浪潮来袭,职业竞争的新风口已至?

1. RPA职业定义与范畴 1.1 RPA核心概念 RPA&#xff08;Robotic Process Automation&#xff0c;机器人流程自动化&#xff09;是一种通过软件机器人模拟人类操作&#xff0c;实现重复性、规律性任务自动化的技术。它能够自动执行诸如数据输入、文件处理、系统操作等任务&…...

【Elasticsearch】字段别名

在 Elasticsearch 中&#xff0c;字段别名&#xff08;Field Alias&#xff09;主要用于查询和检索阶段&#xff0c;而不是直接用于写入数据。 为什么不能通过字段别名写入数据&#xff1f; 字段别名本质上是一个映射关系&#xff0c;它将别名指向实际的字段。Elasticsearch …...

【Linux笔记】防火墙firewall与相关实验(iptables、firewall-cmd、firewalld)

一、概念 1、防火墙firewall Linux 防火墙用于控制进出系统的网络流量&#xff0c;保护系统免受未授权访问。常见的防火墙工具包括 iptables、nftables、UFW 和 firewalld。 防火墙类型 包过滤防火墙&#xff1a;基于网络层&#xff08;IP、端口、协议&#xff09;过滤流量&a…...

人工智能解析:技术革命下的认知重构

当生成式AI能够自主创作内容、设计方案甚至编写代码时&#xff0c;我们面对的不仅是工具革新&#xff0c;更是一场关于智能本质的认知革命。人工智能解析的核心&#xff0c;在于理解技术如何重塑人类解决问题和创造价值的底层逻辑——这种思维方式的转变&#xff0c;正成为数字…...

Neo4j实现向量检索

最近因为Dify、RagFlow这样的智能体的镜像拉取的速度实在太麻烦&#xff0c;一狠心想实现自己的最简单的RAG。 因为之前图数据库使用到了neo4j&#xff0c;查阅资料才发现​​Neo4j从5.11版本开始支持向量索引&#xff0c;提供一个真实可用的单元测试案例。 Neo4j建向量索引表…...

SpringBoot外部化配置

外部化配置&#xff08;Externalized Configuration&#xff09;是指将应用的配置从代码中剥离出来&#xff0c;放在外部文件或环境中进行管理的一种机制。 通俗地说&#xff0c;就是你不需要在代码里写死配置信息&#xff08;比如数据库账号、端口号、日志级别等&#xff09;…...

Gut(IF: 23.1)|深度多组学破局肝癌免疫联合治疗耐药的空间微环境图谱

肝细胞癌&#xff08;HCC&#xff09;是癌症相关死亡的主要原因之一&#xff0c;晚期患者预后极差。近年来&#xff0c;免疫检查点抑制剂&#xff08;ICI&#xff09;联合治疗&#xff08;如抗CTLA-4的tremelimumab和抗PD-L1的durvalumab&#xff09;已成为晚期HCC的一线治疗方…...