线程池介绍,分类,实现(工作原理,核心组成,拒绝策略),固态线程池的实现+详细解释(支持超时取消机制和不同的拒绝策略)

目录

线程池

介绍

分类

实现

工作原理

核心组成

拒绝策略 

固态线程池

功能

std::future

实现

拒绝策略支持

提交任务

超时取消

用户检测取消

安全销毁

代码

测试


线程池

介绍

线程池(图解,本质,模拟实现代码),添加单例模式(懒汉思路+代码)_线程池单例-CSDN博客

  • 包括线程池介绍+使用pthread库接口

这里我们就使用c++11中的thread库来实现一下,并且引入更多特性

分类

固定大小线程池(Fixed-size Thread Pool)

  • 线程池中的线程数在初始化时被设定并保持固定,不会根据负载自动扩展或收缩。

  • 适用于负载较为平稳的场景,例如Web服务器、数据库连接池等。

动态线程池(DynamicThread Pool)

  • 线程池的线程数是动态变化的,根据任务的数量来增加或减少线程数。

  • 当线程池中没有任务时,线程会被回收(通常有一个最大线程数限制)。

  • 适用于任务量不稳定、并发变化较大的场景,如文件处理、短时间的批量任务等。

单线程化线程池(Single-threaded Thread Pool)

  • 线程池中只有一个线程,所有任务都会按顺序提交给这个线程执行。

  • 适用于串行化任务,如日志记录、事件驱动任务等。

调度线程池(Scheduled Thread Pool)

  • 该线程池支持任务的延迟执行和周期性执行。

  • 通常用于定时任务或周期性任务,例如定时清理缓存等。

实现

工作原理

  • 线程池初始化:线程池初始化时创建一定数量的工作线程,并使其处于“等待”状态,准备执行任务
  • 任务提交:当有新任务提交时,线程池将任务放入任务队列中
  • 任务执行:线程池中的空闲线程从任务队列中取出任务并执行
  • 任务完成:任务执行完后,线程回到等待状态,准备接收新的任务
  • 线程销毁:当线程池中的线程闲置超过一定时间,线程池可以销毁一些线程以释放系统资源(适用于动态线程池)

核心组成

一个线程池一般由以下几个核心组件组成:

线程工作线程集合

  • 一组预先创建的固定数量或动态伸缩(包括核心线程和临时创建)的线程
  • 每个线程循环从任务队列中获取任务执行

任务队列

  • 用于存放等待执行的任务,一般为线程安全的队列(如 std::queue + mutex),支持任务入队/出队

任务提交接口

  • 对外暴露的函数接口,用于将任务提交到线程池,如 submit()

同步机制

  • 用于保护共享资源和协调线程间关系(每个线程都要访问任务队列)
  • 常用 mutex, condition_variable, atomic

任务拒绝策略(Rejection Policy)

  • 当队列已满时,决定如何处理新任务(下面介绍)

生命周期管理(Shutdown & Destruction)

  • 控制线程池的启动、停止、销毁,确保线程安全退出并释放资源

可选的任务取消机制

  • 支持任务在执行中被取消(比如因当前任务执行超时等原因)

可选的任务超时机制

  • 为每个任务设置执行超时,到期未完成的任务自动取消或通知中断

拒绝策略 

抛出异常,拒绝执行(默认策略)

  • 抛出 RejectedExecutionException 异常,告知任务无法执行
  • 适合你希望及时发现问题并中止提交任务

由提交任务的线程执行

  • 将任务回退给调用者,即提交任务的线程执行任务,而不是交给线程池中的线程处理
  • 可以起到“削峰”作用,但影响主线程性能(会阻塞提交线程)

静默丢弃

  • 丢弃无法执行的任务,不抛出异常
  • 适合对任务可丢弃、无严重后果的场景(如日志)

丢弃队列中最旧的任务,然后尝试重新提交当前任务

  • 适合任务有时效性(如更新 UI、股票报价)

固态线程池

功能

支持:

  • 固定线程数: 构造时指定线程数,固定数量线程常驻执行任务

  • 有界队列: 支持设置任务队列最大容量,避免过载

  • 拒绝策略支持: 支持 BLOCK(等待)、DISCARD(丢弃)、THROW(抛异常)三种策略

  • 提交任务: 提交 void(exec_context) 类型任务,通过适配器转换为 void() 并存入任务队列,最终返回 std::future<void>

  • 超时取消: 自动按任务类型 (因为我这里是把自己写的搜索引擎项目中增设的线程池作为例子,所以这里的类型就分为建立索引,搜索,搜索联想) , 设置超时时间,到时通知任务取消

  • 用户检测取消: 任务内部可用 canceled() 检测取消请求并安全退出

  • 安全销毁: 析构时安全停止所有线程并等待退出

  • 线程安全: 所有关键资源由 mutexcondition_variable 保护,支持多线程并发提交任务

std::future

std::future<T> 是 C++11 引入的标准库类型

  • 可以异步获取一个任务的执行结果
  • 作用 -- 当你把一个任务提交给线程池时,这个任务可能要等一会儿才能执行(毕竟线程有限,排队中),那么你就需要一个东西来 “将来拿到结果”

实现

拒绝策略支持

定义了一个枚举类

    enum class RejectionPolicy{BLOCK,DISCARD,THROW};

当任务提交后,根据当前的任务队列使用情况和拒绝策略的设置,决定对任务的处理方式

{std::unique_lock<std::mutex> lock(mtx_);// 阻塞策略:等待有空间if (tasks_.size() >= max_queue_size_ && !stop_){if (reject_policy_ == RejectionPolicy::BLOCK){//因为这里是带谓词的wait,所以即使虚假唤醒也会在条件为真后返回cond_.wait(lock, [this]{ return tasks_.size() < max_queue_size_ || stop_; });}else if (reject_policy_ == RejectionPolicy::DISCARD){throw std::runtime_error("Task queue is full. Task was discarded.");}else if (reject_policy_ == RejectionPolicy::THROW){throw std::runtime_error("Task queue is full.");}}if (stop_){throw std::runtime_error("ThreadPool is stopping. Cannot accept new tasks.");}tasks_.emplace([taskWrapper](){ (*taskWrapper)(); });}

这里的静默丢弃

  • 虽然定义上是不抛出异常,但为了调用方知道任务没被接收,还是加上了

这里的while循环是为了防止虚假唤醒:

提交任务
    // 传入void(exec_context) ,因为内部也需要配合template <class F>std::future<void> submit(const std::string &taskType, F &&f){auto timeout = getTimeoutForTask(taskType);auto cancellableTask = std::make_shared<CancellableTask>();cancellableTask->func = std::forward<F>(f);// 管理任务执行结果auto taskWrapper = std::make_shared<std::packaged_task<void()>>([cancellableTask](){ (*cancellableTask)(); });// 从packaged_task中获取一个关联的future对象std::future<void> taskFuture = taskWrapper->get_future();{std::unique_lock<std::mutex> lock(mtx_);// 阻塞策略:等待有空间if (tasks_.size() >= max_queue_size_ && !stop_){if (reject_policy_ == RejectionPolicy::BLOCK){cond_.wait(lock, [this]{ return tasks_.size() < max_queue_size_ || stop_; });}else if (reject_policy_ == RejectionPolicy::DISCARD){throw std::runtime_error("Task queue is full. Task was discarded.");}else if (reject_policy_ == RejectionPolicy::THROW){throw std::runtime_error("Task queue is full.");}}if (stop_){throw std::runtime_error("ThreadPool is stopping. Cannot accept new tasks.");}tasks_.emplace([taskWrapper](){ (*taskWrapper)(); });}cond_.notify_one();// 启动一个后台线程监控超时取消std::thread([taskFuture = std::move(taskFuture),controller = cancellableTask->controller, timeout]() mutable{if (taskFuture.wait_for(timeout) == std::future_status::timeout) {controller->notify_cancel();std::cerr << "[Timeout] Task exceeded time limit and was cancelled.\n";} }).detach();return taskFuture;}

这里因为任务队列中使用的void()类型,而外部传入的是void(exec_context) (因为需要内部配合停止)

  • 所以需要对类型进行一个转换
  • 也就是通过cancellableTask 这个可取消的任务封装器,实际是一个无参可调用对象,内部实例化一个exec_context ,然后调用带参数版本的函数
  • 最后将 (*taskWrapper)() 放入队列,实际就是调用了cancellableTask的operator()()
  • 于是实现了将void(exec_context) -> void()
超时取消

首先,是定义了两个模块,分别的作用是 --  取消状态的设置 和 作为对外接口

// 控制标识符
struct exec_controller
{bool notify_cancel() { return _should_cancel.exchange(true); }bool should_cancel() const { return _should_cancel; }private:std::atomic<bool> _should_cancel{false};
};
// 判断是否被取消
struct exec_context
{exec_context(std::shared_ptr<exec_controller> impl): _impl(std::move(impl)) {}bool canceled() const { return _impl->should_cancel(); }private:std::shared_ptr<exec_controller> _impl;
};
  • 使用原子变量避免竞态条件
  • 读取控制器可以被多个任务共享 (通过智能指针控制生命周期)

其次,定义了一个可取消的任务封装器

struct CancellableTask
{// 封装一个取消控制器std::shared_ptr<exec_controller> controller =std::make_shared<exec_controller>();// 将取消上下文封装进普通任务中std::function<void(exec_context)> func;void operator()(){exec_context ctx{controller};func(ctx);}
};

然后,还定义了根据不同任务类型,返回对应超时时间的函数

 static std::chrono::millisecondsgetTimeoutForTask(const std::string &taskType){if (taskType == ns_helper::TASK_TYPE_BUILD_INDEX){return std::chrono::seconds(120);}else if (taskType == ns_helper::TASK_TYPE_PERSIST_INDEX){return std::chrono::seconds(4 * 3600);}else if (taskType == ns_helper::TASK_TYPE_SEARCH ||taskType == ns_helper::TASK_TYPE_AUTOCOMPLETE){return std::chrono::seconds(1);}else{return std::chrono::seconds(1);}}
  • 我这里因为是搜索引擎,并且没有做性能优化,所以设置的时间比较长(跪)

并且,启动了一个后台线程去监控任务状态

        // 启动一个后台线程监控超时取消std::thread([taskFuture = std::move(taskFuture),controller = cancellableTask->controller, timeout]() mutable{if (taskFuture.wait_for(timeout) == std::future_status::timeout) {controller->notify_cancel();std::cerr << "[Timeout] Task exceeded time limit and was cancelled.\n";} }).detach();
  • 这里就只讲一下lambda内部逻辑,传参会在下面的核心逻辑介绍
  • 总之,这个线程会等待任务完成timeout秒,如果未完成,则任务超时 -> 设置取消控制器的标识符
  • 这里还设置了调用了datach,让它和主线程分离(也就是后台运行)

最后,就是核心逻辑了

    // 传入void(exec_context) ,因为内部也需要配合template <class F>std::future<void> submit(const std::string &taskType, F &&f){auto timeout = getTimeoutForTask(taskType);auto cancellableTask = std::make_shared<CancellableTask>();cancellableTask->func = std::forward<F>(f);// 管理任务执行结果auto taskWrapper = std::make_shared<std::packaged_task<void()>>([cancellableTask](){ (*cancellableTask)(); });// 从packaged_task中获取一个关联的future对象std::future<void> taskFuture = taskWrapper->get_future();{std::unique_lock<std::mutex> lock(mtx_);// 阻塞策略:等待有空间if (tasks_.size() >= max_queue_size_ && !stop_){if (reject_policy_ == RejectionPolicy::BLOCK){cond_.wait(lock, [this]{ return tasks_.size() < max_queue_size_ || stop_; });}else if (reject_policy_ == RejectionPolicy::DISCARD){throw std::runtime_error("Task queue is full. Task was discarded.");}else if (reject_policy_ == RejectionPolicy::THROW){throw std::runtime_error("Task queue is full.");}}if (stop_){throw std::runtime_error("ThreadPool is stopping. Cannot accept new tasks.");}tasks_.emplace([taskWrapper](){ (*taskWrapper)(); });}cond_.notify_one();// 启动一个后台线程监控超时取消std::thread([taskFuture = std::move(taskFuture),controller = cancellableTask->controller, timeout]() mutable{if (taskFuture.wait_for(timeout) == std::future_status::timeout) {controller->notify_cancel();std::cerr << "[Timeout] Task exceeded time limit and was cancelled.\n";} }).detach();return taskFuture;}
  • 其实大头设计就是那些组件,把它们组合起来使用+任务代码中显式检测取消标识,就能实现超时取消机制  -- 在后台单独启动一个线程,监控提交的任务是否在规定的超时时间内完成,如果超时则通知取消
用户检测取消

传入的任务也需要配合(检测取消标识符,为真时结束执行)

void test_cancel_task(exec_context ctx)
{std::cout << "Task started.\n";for (int i = 0; i < 50; ++i){if (ctx.canceled()){std::cout << "Task detected cancellation, exiting early.\n";return;}std::this_thread::sleep_for(std::chrono::milliseconds(100));std::cout << "Task working... step " << i << "\n";}std::cout << "Task completed normally.\n";
}
安全销毁
 explicit FixedThreadPool(size_t thread_count, size_t max_queue_size = 1000,RejectionPolicy policy = RejectionPolicy::BLOCK): max_queue_size_(max_queue_size), reject_policy_(policy), stop_(false){for (size_t i = 0; i < thread_count; ++i){workers_.emplace_back([this]{while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(mtx_);cond_.wait(lock, [this] { return stop_ || !tasks_.empty(); });if (stop_ && tasks_.empty())return;task = std::move(tasks_.front());tasks_.pop();}task();} });}}
~FixedThreadPool(){{std::unique_lock<std::mutex> lock(mtx_);stop_ = true;}cond_.notify_all();for (std::thread &worker : workers_){if (worker.joinable()){worker.join();}}}
  • 释放资源前,先设置停止标志,并唤醒所有线程,让线程检测到停止标识后,安全地将队列中的遗留任务处理完毕,然后释放线程资源

代码

#include <atomic>
#include <chrono>
#include <condition_variable>
#include <exception>
#include <functional>
#include <future>
#include <iostream>
#include <mutex>
#include <optional>
#include <queue>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>#include "../code/assistance.hpp"// --- exec control logic ---struct exec_controller
{bool notify_cancel() { return _should_cancel.exchange(true); }bool should_cancel() const { return _should_cancel; }private:std::atomic<bool> _should_cancel{false};
};struct exec_context
{exec_context(std::shared_ptr<exec_controller> impl): _impl(std::move(impl)) {}bool canceled() const { return _impl->should_cancel(); }private:std::shared_ptr<exec_controller> _impl;
};// --- CancellableTask ---struct CancellableTask
{std::shared_ptr<exec_controller> controller =std::make_shared<exec_controller>();std::function<void(exec_context)> func;void operator()(){exec_context ctx{controller};func(ctx);}
};// --- FixedThreadPool ---class FixedThreadPool
{
public:enum class RejectionPolicy{BLOCK,DISCARD,THROW};explicit FixedThreadPool(size_t thread_count, size_t max_queue_size = 1000,RejectionPolicy policy = RejectionPolicy::BLOCK): max_queue_size_(max_queue_size), reject_policy_(policy), stop_(false){for (size_t i = 0; i < thread_count; ++i){workers_.emplace_back([this]{while (true) {std::function<void()> task;{std::unique_lock<std::mutex> lock(mtx_);cond_.wait(lock, [this] { return stop_ || !tasks_.empty(); });if (stop_ && tasks_.empty())return;task = std::move(tasks_.front());tasks_.pop();}task();} });}}~FixedThreadPool(){{std::unique_lock<std::mutex> lock(mtx_);stop_ = true;}cond_.notify_all();for (std::thread &worker : workers_){if (worker.joinable()){worker.join();}}}template <class F>std::future<void> submit(const std::string &taskType, F &&f){auto timeout = getTimeoutForTask(taskType);auto cancellableTask = std::make_shared<CancellableTask>();cancellableTask->func = std::forward<F>(f);auto taskWrapper = std::make_shared<std::packaged_task<void()>>([cancellableTask](){ (*cancellableTask)(); });std::future<void> taskFuture = taskWrapper->get_future();{std::unique_lock<std::mutex> lock(mtx_);// 阻塞策略:等待有空间if (reject_policy_ == RejectionPolicy::BLOCK){cond_.wait(lock,[this]{ return tasks_.size() < max_queue_size_ || stop_; });}// 丢弃策略:抛出异常(不再返回默认构造的 future)else if (reject_policy_ == RejectionPolicy::DISCARD){if (tasks_.size() >= max_queue_size_){throw std::runtime_error("Task queue is full. Task was discarded.");}}// 异常策略:同样抛出异常else if (reject_policy_ == RejectionPolicy::THROW){if (tasks_.size() >= max_queue_size_){throw std::runtime_error("Task queue is full.");}}if (stop_){throw std::runtime_error("ThreadPool is stopping. Cannot accept new tasks.");}tasks_.emplace([taskWrapper](){ (*taskWrapper)(); });}cond_.notify_one();// 启动一个后台线程监控超时取消std::thread([taskFuture = std::move(taskFuture),controller = cancellableTask->controller, timeout]() mutable{if (taskFuture.wait_for(timeout) == std::future_status::timeout) {controller->notify_cancel();std::cerr << "[Timeout] Task exceeded time limit and was cancelled.\n";} }).detach();return taskFuture;}private:static std::chrono::millisecondsgetTimeoutForTask(const std::string &taskType){if (taskType == ns_helper::TASK_TYPE_BUILD_INDEX){return std::chrono::seconds(120);}else if (taskType == ns_helper::TASK_TYPE_PERSIST_INDEX) // ✅ 修复这里{return std::chrono::seconds(4 * 3600);}else if (taskType == ns_helper::TASK_TYPE_SEARCH ||taskType == ns_helper::TASK_TYPE_AUTOCOMPLETE){return std::chrono::seconds(1);}else{return std::chrono::seconds(1);}}std::vector<std::thread> workers_;std::queue<std::function<void()>> tasks_;std::mutex mtx_;std::condition_variable cond_;bool stop_;size_t max_queue_size_;RejectionPolicy reject_policy_;
};
测试
void test_cancel_task(exec_context ctx)
{std::cout << "Task started.\n";for (int i = 0; i < 50; ++i){if (ctx.canceled()){std::cout << "Task detected cancellation, exiting early.\n";return;}std::this_thread::sleep_for(std::chrono::milliseconds(100));std::cout << "Task working... step " << i << "\n";}std::cout << "Task completed normally.\n";
}int main()
{FixedThreadPool fp(2, 10, FixedThreadPool::RejectionPolicy::BLOCK);auto future = fp.submit("search", test_cancel_task);try{if (future.valid()){future.get();}}catch (const std::exception &e){std::cout << "Task threw exception: " << e.what() << "\n";}return 0;
}

动态线程池会在另一篇讲(我还没写 1 - 1) ,大家也可以帮我纠一纠错(跪倒)

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

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

相关文章

纺线机与PLC通讯故障?ETHERCAT/CANopen网关秒解协议难题

在纺织行业智能化转型浪潮中&#xff0c;设备间高效通信是实现自动化生产的关键。JH-ECT009疆鸿智能EtherCAT转CANopen协议转换网关&#xff0c;凭借出色的协议适配能力&#xff0c;成功架起倍福PLC与自动纺线机间的通信桥梁&#xff0c;为纺织厂自动化生产注入强劲动力。 纺织…

深度剖析并发I/O模型select、poll、epoll与IOCP核心机制

核心概要&#xff1a;select、poll、epoll 和 IOCP 是四种用于提升服务器并发处理能力的I/O模型或机制。前三者主要属于I/O多路复用范畴&#xff0c;允许单个进程或线程监视多个I/O流的状态&#xff1b;而 IOCP 则是一种更为彻底的异步I/O模型。 一、引言&#xff1a;为何需要这…

microsoft中word如何添加个人签名

https://support.microsoft.com/zh-cn/office/%E6%8F%92%E5%85%A5%E7%AD%BE%E5%90%8D-f3b3f74c-2355-4d53-be89-ae9c50022730 插入签名图片 图片格式选择裁剪合适的大小 使用的签名如果不是白色纸张的话可以重新着色 依次点击图片格式——颜色——重新着色——黑白50% 设置透…

linux初识--基础指令

Linux下基础指令 ls 指令 语法&#xff1a; ls [ 选项 ] [ ⽬录或⽂件 ] 功能&#xff1a;对于⽬录&#xff0c;该命令列出该⽬录下的所有⼦⽬录与⽂件。对于⽂件&#xff0c;将列出⽂件名以及其他信 息。 常⽤选项&#xff1a; -a 列出⽬录下的所有⽂件&#xff0c;包括以…

实战:Dify智能体+Java=自动化运营工具!

我们在运营某个圈子的时候&#xff0c;可能每天都要将这个圈子的“热门新闻”发送到朋友圈或聊天群里&#xff0c;但依靠传统的实现手段非常耗时耗力&#xff0c;我们通常要先收集热门新闻&#xff0c;再组装要新闻内容&#xff0c;再根据内容设计海报等。 那怎么才能简化并高…

RabbitMQ可靠传输——持久性、发送方确认

一、持久性 前面学习消息确认机制时&#xff0c;是为了保证Broker到消费者直接的可靠传输的&#xff0c;但是如果是Broker出现问题&#xff08;如停止服务&#xff09;&#xff0c;如何保证消息可靠性&#xff1f;对此&#xff0c;RabbitMQ提供了持久化功能&#xff1a; 持久…

(Java基础笔记vlog)Java中常见的几种设计模式详解

前言&#xff1a; 在 Java 编程里&#xff0c;设计模式是被反复使用、多数人知晓、经过分类编目的代码设计经验总结。他能帮助开发者更高效地解决常见问题&#xff0c;提升代码的可维护性、可扩展性和复用性。下面介绍Java 中几种常见的设计模式。 单例模式&#xff08;Singlet…

(8)Spring Boot 原生镜像支持

Spring Boot 原生镜像支持 👉 点击展开题目 在Spring Boot 3.x中,如何设计一个支持GraalVM原生镜像的微服务?需要特别注意哪些限制? 📌 Spring Boot 3.x 原生镜像概述 Spring Boot 3.x 通过 Spring Native 项目提供了对 GraalVM 原生镜像的一流支持,使开发者能够将 S…

不使用SOAP,从PDF表单连接数据库

不使用SOAP协议&#xff0c;通过XFDF格式实现PDF表单与数据库交互的方法。该方法兼容免费的Adobe Reader&#xff0c;且无需特殊权限设置。 背景与问题 历史方案: Adobe曾提供ADBC接口&#xff08;基于ODBC&#xff09;&#xff0c;但在Acrobat 9后被移除。SOAP方案在免费版Rea…

HTTP由浅入深

文章目录 概述特点URL HTTP 与 HTTPS概述HTTP 工作原理HTTPS 的作用区别总结 请求报文请求行常见请求方法请求头请求体Content-Type 详解常见场景 Content-Type 对应关系 响应报文响应行状态码详解1xx&#xff1a;信息响应&#xff08;Informational&#xff09;2xx&#xff1a…

Redis淘汰策略

Redis有八种淘汰策略 noeviction &#xff1a;不进行淘汰&#xff0c;直接报错。allkeys-lru &#xff1a;随机淘汰最久未使用的键。volatile-lru &#xff1a;从设置了过期时间的键中&#xff0c;随机淘汰最久未使用的键。allkeys-random &#xff1a;随机淘汰某个键。volati…

Maven打包SpringBoot项目,因包含SpringBootTest单元测试和Java预览版特性导致打包失败

SpringBoot启用Java预览版特性&#xff08;无测试类&#xff09; 在pom.xml文件中加入以下配置表示启用Java预览版 <plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration>…

Makefile快速入门

简介‌&#xff1a; ‌ Makefile‌ 是一种用于自动化构建和管理软件项目的工具文件&#xff0c;通常与 make 命令配合使用。它通过定义‌规则‌&#xff08;rules&#xff09;来指定如何从源文件生成目标文件&#xff08;如可执行程序或库&#xff09;&#xff0c;并自动…

RISC-V 开发板 MUSE Pi Pro OpenCV结合Gstreamer实时显示CSI摄像头

视频讲解&#xff1a;RISC-V 开发板 MUSE Pi Pro OpenCV结合Gstreamer实时显示CSI摄像头_哔哩哔哩_bilibili RISC-V 开发板 MUSE Pi Pro OpenCV结合Gstreamer实时显示CSI摄像头 安装opencv相关库 sudo apt install libopencv-dev python3 python3-opencv 测试使用的CSI摄像头…

如何用JAVA手写一个Tomcat

一、初步理解Tomcat Tomcat是什么&#xff1f; Tomcat 是一个开源的 轻量级 Java Web 应用服务器&#xff0c;核心功能是 运行 Servlet/JSP。 Tomcat的核心功能&#xff1f; Servlet 容器&#xff1a;负责加载、实例化、调用和销毁 Servlet。 HTTP 服务器&#xff1a;监听端口…

短剧系统开发与抖音生态融合:短视频时代的新风口与商业机遇

在短视频内容井喷的时代&#xff0c;“短剧”作为一种新兴内容形态&#xff0c;正以惊人的速度抢占用户注意力。抖音、快手等平台日均播放量破亿的短剧作品&#xff0c;不仅催生了新的内容创作风口&#xff0c;更推动了短剧系统开发的巨大市场需求。本文将深度解析短剧系统开发…

《云原生安全攻防》-- K8s日志审计:从攻击溯源到安全实时告警

当K8s集群遭受入侵时&#xff0c;安全管理员可以通过审计日志进行攻击溯源&#xff0c;通过分析攻击痕迹&#xff0c;我们可以找到攻击者的入侵行为&#xff0c;还原攻击者的攻击路径&#xff0c;以便修复安全问题。 在本节课程中&#xff0c;我们将介绍如何配置K8s审计日志&am…

3dczml时间动态图型场景

在cesium中我们了可以使用czml数据来生成可以随时间变化而变化的物体. 首先导入czml数据 设置时间范围 id: "point" //物体在什么时间范围可用 availability:"2012-08-04T16:00:00Z/2012-08-04T16:05:00Z"position:{ //设置物体的起始时间 epoch:"…

超小多模态视觉语言模型MiniMind-V 训练

简述 MiniMind-V 是一个超适合初学者的项目&#xff0c;让你用普通电脑就能训一个能看图说话的 AI。训练过程就像教小孩&#xff1a;先准备好图文材料&#xff08;数据集&#xff09;&#xff0c;教它基础知识&#xff08;预训练&#xff09;&#xff0c;再教具体技能&#xf…

01-jenkins学习之旅-window-下载-安装-安装后设置向导

1 jenkins简介 百度百科介绍&#xff1a;Jenkins是一个开源软件项目&#xff0c;是基于Java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件项目可以进行持续集成。 [1] Jenkins官网地址 翻译&…