Java定时任务实现方案(五)——时间轮

news2025/2/1 4:32:58

时间轮

这篇笔记,我们要来介绍实现Java定时任务的第五个方案,使用时间轮,以及该方案的优点和缺点。
​ 时间轮是一种高效的定时任务调度算法,特别适用于大量定时任务的场景。时间轮的定时任务实现,可以使用DelayQueue作为基础。

​ 在使用时间轮算法之前,我要来简单介绍一下时间轮的一些概念,便于大家理解。

​ 我们可以把时间轮想象成一个时钟,这个时钟被划分为12个格子,每个格子代表一段时间间隔,我们假设是1000ms(1s),每个格子里存放着这个时间段内需要执行的所有定时任务。时钟上有一根指针,当指针指向哪个格子时,格子内的定时任务就可以开始执行或者准备执行了,每过一个时间间隔,指针就向前移动一格,执行下一个时间段的定时任务。

​ 在我们上面的举例当中,12个格子叫做时间槽,时间轮可以被划分为多个固定大小的时间槽,每一个时间槽代表一个时间段;时钟上的指针,用来指示当前需要执行定时任务的时间槽;

​ 我们日常中的时钟,是有三个指针的,我们的时间轮也可以拓展成多级时间轮,支持更长时间的定时任务调度。

实现
1.单个时间槽的实现

​ 因为我们要使用DelayQueue作为基础实现时间轮,所以我们首先要有一个实现了Delay接口的类来承接我们的单个定时任务,如果对如何使用DelayQueue不了解的,可以去看一下我的另一篇关于使用DelayQueue实现定时任务的小作文哦。

    private static class TimerTask implements Delayed {
        private final Runnable task;
        private final long expiration;

        public TimerTask(Runnable task, long expiration) {
            this.task = task;
            this.expiration = expiration;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(expiration - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            return Long.compare(this.expiration, ((TimerTask) o).expiration);
        }

        public void run() {
            task.run();
        }
    }

​ 接着,因为我们的时间轮是处理一个时间槽内的一批定时任务,所以我们还需要一个存储一个时间槽内所有定时任务的集合类,或者说一个逻辑上的时间槽任务类。

​ 这个逻辑时间槽任务类,我们可以把时间槽也当成一个定时任务,存放在时间轮中,因此,也要实现Delay接口,任务执行的时间,就是这个时间槽的表示的时间段的起始时间。

​ 在这个时间槽类中,我们其实就是使用DelayQueue来存取我们单个的定时任务,说白了,就是将DelayQueue实现定时任务的方法进行封装,我们要对外暴露添加任务和执行任务的方法,为了能够实现时间槽的复用,当时间槽中的定时任务清空之后,我们要重置这个时间槽的时间。

       /**
     * TimerTaskList类实现了Delayed接口,用于管理一组具有延迟执行需求的任务
     * 
     */
    private static class TimerTaskList implements Delayed {
        // 任务的过期时间,即任务应该被执行的时间点
        private long expiration;
        private List<TimerTask> tasks;
        // 使用DelayQueue来存储具有延迟执行需求的TimerTask对象
        private DelayQueue<TimerTask> queue = new DelayQueue<>();
        // ExecutorService用于执行任务,它是在类初始化时通过构造函数传入的
        private final ExecutorService executorService;

        /**
         * 构造函数,初始化ExecutorService
         *
         * @param executorService 用于执行任务的线程池
         */
        public TimerTaskList(ExecutorService executorService) {
            this.executorService = executorService;
            this.tasks = new ArrayList<>();
        }

        /**
         * 向队列中添加一个新的TimerTask任务
         *
         * @param task 要添加的TimerTask对象
         */
        public void addTask(TimerTask task) {
            tasks.add(task);
            queue.offer(task);
        }

        /**
         * 设置任务的过期时间
         * 只有当expiration尚未设置(即值为0)时,才更新expiration值
         *
         * @param expiration 任务的过期时间
         * @return 如果expiration成功设置,则返回true;否则返回false
         */
        public boolean setExpiration(long expiration) {
            if (this.expiration == 0) {
                this.expiration = expiration;
                return true;
            }
            return false;
        }
        /**
         * 清除所有任务并重置过期时间
         *
         * 本方法旨在清除所有当前持有的任务,并将过期时间重置为0
         * 这在需要重新初始化或清理资源时特别有用
         */
        public void clearTasks(){
            // 清除所有任务
            tasks.clear();
            // 重置过期时间为0,表示没有过期时间
            expiration = 0;
        }
        public List<TimerTask> getTasks(){
            return tasks;
        }
        /**
         * 执行所有任务
         *
         * 此方法遍历任务列表,并依次执行每个任务的方法run
         * 在所有任务执行完毕后,调用clearTasks方法清除任务列表
         */
        public void executeTasks(){
            // 遍历任务列表
            for(TimerTask task:tasks){
                // 执行任务的run方法
                task.run();
            }
            // 所有任务执行完毕后,清除任务列表
            clearTasks();
        }

        /**
         * 执行队列中的所有任务
         * 如果队列不为空,则通过executorService执行每个任务
         * 在所有任务执行完毕后,清除expiration值
         */
        public void run() {
            if (!queue.isEmpty()) {
                executorService.execute(() -> {
                    while (!queue.isEmpty()) {
                        try {
                            queue.take().run();
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    }
                    clearExpiration();
                });
            }
        }

        /**
         * 获取当前设置的过期时间
         *
         * @return 当前的expiration值
         */
        public long getExpiration() {
            return expiration;
        }

        /**
         * 清除过期时间设置,将expiration重置为0
         */
        public void clearExpiration() {
            expiration = 0;
        }

        /**
         * 实现Delayed接口的getDelay方法
         * 计算当前时间与过期时间之间的差值,以确定延迟时间
         *
         * @param unit 时间单位
         * @return 剩余的延迟时间,以指定的时间单位表示
         */
        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(expiration - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        /**
         * 实现Delayed接口的compareTo方法
         * 用于比较两个TimerTaskList对象的过期时间
         *
         * @param o 另一个Delayed对象
         * @return 如果当前对象的过期时间小于、等于或大于参数对象的过期时间,则分别返回负数、零或正数
         */
        @Override
        public int compareTo(Delayed o) {
            return Long.compare(this.expiration, ((TimerTaskList) o).expiration);
        }
    }
2.将时间槽组成时间轮

​ 完成了单个时间槽的实现之后,剩下的就简单很多了,将上面的两个类作为时间轮类的内部类,将时间槽作为时间轮的一个定时任务来组成我们的时间轮。根据我们前面对于时间轮的描述,时间轮其实就是多个时间槽围成一圈变成了一个时间轮。我们在学数据结构的时候,学过循环队列,把循环队列中的元素换成我们的时间槽,再加上一个指针指向时间槽,就真正构成了一个单层的时间轮了。注意,这里的指针得使用原子类来保证并发安全,因为我们的时间轮可能被多个线程同时使用。

​ 如果时间轮的每一个时间槽存的也是一个时间轮,那么多构成了多级时间轮,对于多级时间轮,我们只需要在最低级的时间轮中放置定时任务,不需要放置子轮,对于高级的时间轮,我们只需要放置子轮,不需要放置定时任务。

​ 我们的时间轮要对外提供启动时间轮的方法,添加定时任务到多级时间轮中的方法,更新移动指针的方法,以及执行时间轮中一批定时任务的方法。

public class TimingWheel {
    // 每个时间槽的时间间隔,单位毫秒
    private static final int TICK_DURATION = 1000;
    // 时间轮的大小,即每个时间轮包含的时间槽数量
    private static final int WHEEL_SIZE = 20;
    // 子时间轮列表,用于处理超过当前时间轮处理能力的任务
    private final List<TimingWheel> subWheels;
    // 当前时间轮的级别,从0开始,级别越高,表示处理的时间跨度越大
    private final int level;
    // 最大时间轮级别,用于确定时间轮的深度
    private final int maxLevel;
    // 共享的延迟队列,用于存储所有到期的任务列表
    private final DelayQueue<TimerTaskList> sharedQueue;
    // 时间槽数组,用于存储任务列表
    private final TimerTaskList[] buckets = new TimerTaskList[WHEEL_SIZE];
    // 时间轮的当前刻度,使用原子长整型确保线程安全
    private final AtomicLong tick = new AtomicLong(0);
    // 任务执行线程池
    private final ExecutorService executorService;
    
    /**
     * 构造函数,初始化时间轮
     *
     * @param maxLevel 最大时间轮级别,用于确定时间轮的深度
     */
    public TimingWheel(int maxLevel){
        this.level = maxLevel;
        this.maxLevel = maxLevel;
        this.subWheels = new ArrayList<>();
        this.sharedQueue = new DelayQueue<>();
        this.executorService = Executors.newFixedThreadPool(WHEEL_SIZE+1);
        if(maxLevel <= 0){
            for(int i = 0;i < WHEEL_SIZE;i++){
                buckets[i] = new TimerTaskList(this.executorService);
            }
        }else{
            for(int i = 0; i < WHEEL_SIZE;i++){
                subWheels.add(new TimingWheel(maxLevel - 1, maxLevel,this.sharedQueue,this.executorService));
            }
        }
    }

    /**
     * 私有构造函数,用于创建子时间轮
     *
     * @param level 当前时间轮的级别
     * @param maxLevel 最大时间轮级别
     * @param sharedQueue 共享的延迟队列
     * @param executorService 任务执行线程池
     */
    private TimingWheel(int level, int maxLevel,DelayQueue<TimerTaskList> sharedQueue,ExecutorService executorService) {
        this.level = level;
        this.maxLevel = maxLevel;
        this.subWheels = new ArrayList<>();
        this.sharedQueue = sharedQueue;
        this.executorService = executorService;
        if (level > 0) {
            for (int i = 0; i < WHEEL_SIZE; i++) {
                subWheels.add(new TimingWheel(level - 1, maxLevel,this.sharedQueue,this.executorService));
            }
        }else{
            for (int i = 0; i < WHEEL_SIZE; i++) {
                buckets[i] = new TimerTaskList(this.executorService);
            }
        }
    }

    /**
     * 启动时间轮,开始处理任务
     */
    public void start() {
        executorService.execute(() -> {
            while (true) {
                try {
                    TimerTaskList bucket = sharedQueue.take();
                    long ticks = (bucket.getExpiration() - System.currentTimeMillis())/ TICK_DURATION;
                    if (ticks > tick.get()) {
                        Thread.sleep((ticks - tick.get()) * TICK_DURATION);
                    }
                    processTasks(bucket);
                    // 更新时间轮指针
                    tick.set(ticks);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt();
                    break;
                }
            }
        });
    }

    /**
     * 添加任务到时间轮
     *
     * @param task 要添加的任务
     * @param delay 延迟时间,单位毫秒
     */
    public void addTask(Runnable task, long delay) {
        long currentTime = System.currentTimeMillis();
        long expiration = currentTime + delay;
        //计算定时任务要放在哪一个时间槽中
        //下一层的时钟长度(如果level为0,那就是一个槽的时间长度)
        long ts = TICK_DURATION * (long)Math.pow(WHEEL_SIZE,level);
        //总时钟步数
        int ticks = (int) ((delay) /ts);
        int bucketIndex = ticks % WHEEL_SIZE;
        TimerTaskList bucket = buckets[bucketIndex];
        if (level > 0) {
            // 修正传递给子时间轮的延迟时间
            subWheels.get(bucketIndex).addTask(task, delay);
        } else {
            bucket.addTask(new TimerTask(task,expiration));
            if (bucket.setExpiration(expiration)) {
                sharedQueue.offer(bucket);
            }
        }
    }

    /**
     * 处理任务列表中的任务
     *
     * @param bucket 任务列表
     */
    private void processTasks(TimerTaskList bucket) {
        bucket.run();
//        bucket.executeTasks();
        if (level < maxLevel) {
            for (TimingWheel subWheel : subWheels) {
                subWheel.advanceClock();
            }
        }
    }

    /**
     * 推动时间轮前进
     */
    public void advanceClock() {
        tick.incrementAndGet();
        for (TimingWheel subWheel : subWheels) {
            subWheel.advanceClock();
        }
    }

    /**
     * TimerTask类,表示一个具有延迟执行需求的任务
     */
    private static class TimerTask implements Delayed {
        private final Runnable task;
        private final long expiration;

        public TimerTask(Runnable task, long expiration) {
            this.task = task;
            this.expiration = expiration;
        }

        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(expiration - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        @Override
        public int compareTo(Delayed o) {
            return Long.compare(this.expiration, ((TimerTask) o).expiration);
        }

        public void run() {
            task.run();
        }
    }

    /**
     * TimerTaskList类实现了Delayed接口,用于管理一组具有延迟执行需求的任务
     * 它使用DelayQueue来存储这些任务,并在满足执行条件时通过ExecutorService来执行它们
     */
    private static class TimerTaskList implements Delayed {
        // 任务的过期时间,即任务应该被执行的时间点
        private long expiration;
        private List<TimerTask> tasks;
        // 使用DelayQueue来存储具有延迟执行需求的TimerTask对象
        private DelayQueue<TimerTask> queue = new DelayQueue<>();
        // ExecutorService用于执行任务,它是在类初始化时通过构造函数传入的
        private final ExecutorService executorService;

        /**
         * 构造函数,初始化ExecutorService
         *
         * @param executorService 用于执行任务的线程池
         */
        public TimerTaskList(ExecutorService executorService) {
            this.executorService = executorService;
            this.tasks = new ArrayList<>();
        }

        /**
         * 向队列中添加一个新的TimerTask任务
         *
         * @param task 要添加的TimerTask对象
         */
        public void addTask(TimerTask task) {
            tasks.add(task);
            queue.offer(task);
        }

        /**
         * 设置任务的过期时间
         * 只有当expiration尚未设置(即值为0)时,才更新expiration值
         *
         * @param expiration 任务的过期时间
         * @return 如果expiration成功设置,则返回true;否则返回false
         */
        public boolean setExpiration(long expiration) {
            if (this.expiration == 0) {
                this.expiration = expiration;
                return true;
            }
            return false;
        }
        /**
         * 清除所有任务并重置过期时间
         * 
         * 本方法旨在清除所有当前持有的任务,并将过期时间重置为0
         * 这在需要重新初始化或清理资源时特别有用
         */
        public void clearTasks(){
            // 清除所有任务
            tasks.clear();
            // 重置过期时间为0,表示没有过期时间
            expiration = 0;
        }
        public List<TimerTask> getTasks(){
            return tasks;
        }
        /**
         * 执行所有任务
         * 
         * 此方法遍历任务列表,并依次执行每个任务的方法run
         * 在所有任务执行完毕后,调用clearTasks方法清除任务列表
         */
        public void executeTasks(){
            // 遍历任务列表
            for(TimerTask task:tasks){
                // 执行任务的run方法
                task.run();
            }
            // 所有任务执行完毕后,清除任务列表
            clearTasks();
        }

        /**
         * 执行队列中的所有任务
         * 如果队列不为空,则通过executorService执行每个任务
         * 在所有任务执行完毕后,清除expiration值
         */
        public void run() {
            if (!queue.isEmpty()) {
                executorService.execute(() -> {
                    while (!queue.isEmpty()) {
                        try {
                            queue.take().run();
                        } catch (InterruptedException e) {
                            Thread.currentThread().interrupt();
                        }
                    }
                    clearExpiration();
                });
            }
        }

        /**
         * 获取当前设置的过期时间
         *
         * @return 当前的expiration值
         */
        public long getExpiration() {
            return expiration;
        }

        /**
         * 清除过期时间设置,将expiration重置为0
         */
        public void clearExpiration() {
            expiration = 0;
        }

        /**
         * 实现Delayed接口的getDelay方法
         * 计算当前时间与过期时间之间的差值,以确定延迟时间
         *
         * @param unit 时间单位
         * @return 剩余的延迟时间,以指定的时间单位表示
         */
        @Override
        public long getDelay(TimeUnit unit) {
            return unit.convert(expiration - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
        }

        /**
         * 实现Delayed接口的compareTo方法
         * 用于比较两个TimerTaskList对象的过期时间
         *
         * @param o 另一个Delayed对象
         * @return 如果当前对象的过期时间小于、等于或大于参数对象的过期时间,则分别返回负数、零或正数
         */
        @Override
        public int compareTo(Delayed o) {
            return Long.compare(this.expiration, ((TimerTaskList) o).expiration);
        }
    }
}
优点
1.高效的时间管理

​ 时间轮将时间划分为固定大小的时间槽,每个时间槽代表一个时间段,通过指针逐个扫描这些时间槽,可以高效地管理和调度定时任务,避免了频繁的线程唤醒和上下文切换。

2.低延迟和高吞吐量

​ 由于时间轮采用的是批量处理到期任务的方式,因此可以在较低的延迟下出来大量的定时任务,提高系统的吞吐量。

3.扩展性强

​ 时间轮可以通过多级时间轮的设计来支持更长的延迟时间,子时间轮可以处理更长时间的任务,从而使得整个系统能够灵活应对不同延迟需求的任务

4.简单易懂

​ 时间轮的结构和工作原理相对简单,易于理解和实现,这使得我们可以快速上手,并且在调试和维护的时候也更方便

缺点
1.固定的时间槽大小

​ 时间轮的时间槽大小是固定的,这可能导致某些场景下的精度不足。如果时间槽设置得太小,会增加内存占用;如果设置得太大,则可能影响定时任务的精确度。

2.多级时间轮的复杂性

​ 为了处理更长的延迟时间,可以采用多级时间轮的设计,但是这种设计会增加系统的复杂性。

3.任务堆积问题

​ 当大量任务集中在同一个时间槽内时,可能会导致任务堆积,进而影响任务的执行效率和响应时间。

4.时钟漂移

​ 在分布式系统中,不同节点的时钟可能存在偏差,这会影响时间轮的准确性。

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

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

相关文章

乐理笔记——DAY02

三分钟音乐社视频地址&#xff1a; 调号总结篇https://www.bilibili.com/video/BV14p4y1e7TV?spm_id_from333.788.videopod.episodes&vd_source0a2d366696f87e241adc64419bf12cab&p25https://www.bilibili.com/video/BV14p4y1e7TV?spm_id_from333.788.videopod.epis…

企业知识管理在推动组织变革与适应性发展中的关键性作用分析

内容概要 企业知识管理是信息时代背景下的重要管理理念&#xff0c;旨在通过有效地获取、分享和再利用知识&#xff0c;提升组织在变革中的灵活性和创新能力。知识作为企业的重要资产&#xff0c;其有效管理不仅影响到日常运营&#xff0c;更是推动组织变革与适应性发展的核心…

动态规划DP 最长上升子序列模型 登山(题目分析+C++完整代码)

概览检索 动态规划DP 最长上升子序列模型 登山 原题链接 AcWing 1014. 登山 题目描述 五一到了&#xff0c;ACM队组织大家去登山观光&#xff0c;队员们发现山上一共有N个景点&#xff0c;并且决定按照顺序来浏览这些景点&#xff0c;即每次所浏览景点的编号都要大于前一个…

芯片AI深度实战:进阶篇之vim内verilog实时基于AST的自定义检视

本文基于Editor Integration | ast-grep&#xff0c;以及coc.nvim&#xff0c;并基于以下verilog parser(my-language.so&#xff0c;文末下载链接), 可以在vim中实时显示自定义的verilog 匹配。效果图如下&#xff1a; 需要的配置如下&#xff1a; 系列文章&#xff1a; 芯片…

AI 计算的未来:去中心化浪潮与全球竞争格局重塑

引言 人工智能(AI)正以前所未有的速度发展,尤其是大模型训练和推理效率的提升,使得 AI 计算成本迅速下降,呈现出向去中心化演进的趋势。 最新的 DeepSeek r1 模型,以仅 600 万美元 的训练成本,达到了 OpenAI o1 级别的性能,表明 AI 技术正迈向更具普惠性的阶段。这一趋…

JxBrowser 8.2.2 版本发布啦!

JxBrowser 8.2.2 版本发布啦&#xff01; • 已更新 #Chromium 至更新版本 • 实施了多项质量改进 &#x1f517; 点击此处了解更多详情。 &#x1f193; 获取 30 天免费试用。

BWM 世界模型

DGX AGX Ominiverse With Cosmos 功能 1w 张 H100 训练了 3个月 使用 Ray 串流 数据 数据准备 处理 pipeline 数组组成 真实世界的物理数据 训练 1、使用 L1 损失&#xff0c;最小化 输入和重构视频之间的像素级差异 以及基于 VGG19 的一个特征感知损失 2、使用光流的损…

【已解决】windows7虚拟机安装VMtools频繁报错

为了在虚拟机VMware中安装win7&#xff0c;题主先在网上下载了windows7 professional版本的镜像&#xff0c;在vmware中安装vmtools时报错&#xff0c;信息如下 &#xff08;安装程序无法继续&#xff0c;本程序需要您将此虚拟机上安装的操作系统更新到SP1&#xff09; 然后就…

【PyTorch】6.张量运算函数:一键开启!PyTorch 张量函数的宝藏工厂

目录 1. 常见运算函数 个人主页&#xff1a;Icomi 专栏地址&#xff1a;PyTorch入门 在深度学习蓬勃发展的当下&#xff0c;PyTorch 是不可或缺的工具。它作为强大的深度学习框架&#xff0c;为构建和训练神经网络提供了高效且灵活的平台。神经网络作为人工智能的核心技术&…

【浏览器 - Mac实时调试iOS手机浏览器页面】

最近开发个项目&#xff0c;需要在 Mac 电脑上调试 iOS 手机设备上的 Chrome 浏览器&#xff0c;并查看Chrome网页上的 console 信息&#xff0c;本来以为要安装一些插件&#xff0c;没想到直接使用Mac上的Safari 直接可以调试&#xff0c;再此记录下&#xff0c;分享给需要的伙…

PyQt5之QtDesigner的若干配置和使用

1.描述 QtDesigner是一个可视化工具&#xff0c;可以通过该工具设计页面 2.简单使用 1.下载PyQt5-tools pip install pyqt5-tools 2.打开designer.exe文件 我采用的是虚拟环境&#xff0c;该文件位于C:\Users\24715\anaconda3\envs\pyqt\Lib\site-packages\qt5_applicatio…

侯捷C++day01

一个类该准备什么样的数据、函数。才能满足使用这个类人的需求。 inline关键字是建议编译器做inline处理。 private只有本类可以看到。 C创建对象会自动调用构造函数。不可能在程序中显示调用构造函数。不带指针的类多半不用写析构函数。 以下两个重载构造函数会发生错误 不允许…

CTF-web: phar反序列化+数据库伪造 [DASCTF2024最后一战 strange_php]

step 1 如何触发反序列化? 漏洞入口在 welcome.php case delete: // 获取删除留言的路径&#xff0c;优先使用 POST 请求中的路径&#xff0c;否则使用会话中的路径 $message $_POST[message_path] ? $_POST[message_path] : $_SESSION[message_path]; $msg $userMes…

Win11下帝国时代2无法启动解决方法

鼠标右键点图标&#xff0c;选择属性 点开始&#xff0c;输入启用和关闭

GSI快速收录服务:让你的网站内容“上架”谷歌

辛苦制作的内容无法被谷歌抓取和展示&#xff0c;导致访客无法找到你的网站&#xff0c;这是会让人丧失信心的事情。GSI快速收录服务就是为了解决这种问题而存在的。无论是新上线的页面&#xff0c;还是长期未被收录的内容&#xff0c;通过我们的技术支持&#xff0c;都能迅速被…

mysql_init和mysql_real_connect的形象化认识

解析总结 1. mysql_init 的作用 mysql_init 用于初始化一个 MYSQL 结构体&#xff0c;为后续数据库连接和操作做准备。该结构体存储连接配置及状态信息&#xff0c;是 MySQL C API 的核心句柄。 示例&#xff1a; MYSQL *conn mysql_init(NULL); // 初始化连接句柄2. mysql_…

python学opencv|读取图像(四十九)原理探究:使用cv2.bitwise()系列函数实现图像按位运算

【0】基础定义 按位与运算&#xff1a;两个等长度二进制数上下对齐&#xff0c;全1取1&#xff0c;其余取0。 按位或运算&#xff1a;两个等长度二进制数上下对齐&#xff0c;有1取1&#xff0c;其余取0。 按位异或运算&#xff1a; 两个等长度二进制数上下对齐&#xff0c;相…

基础项目实战——学生管理系统(c++)

目录 前言一、功能菜单界面二、类与结构体的实现三、录入学生信息四、删除学生信息五、更改学生信息六、查找学生信息七、统计学生人数八、保存学生信息九、读取学生信息十、打印所有学生信息十一、退出系统十二、文件拆分结语 前言 这一期我们来一起学习我们在大学做过的课程…

春节期间,景区和酒店如何合理用工?

春节期间&#xff0c;景区和酒店如何合理用工&#xff1f; 春节期间&#xff0c;旅游市场将迎来高峰期。景区与酒店&#xff0c;作为旅游产业链中的两大核心环节&#xff0c;承载着无数游客的欢乐与期待。然而&#xff0c;也隐藏着用工管理的巨大挑战。如何合理安排人力资源&a…

Linux Samba 低版本漏洞(远程控制)复现与剖析

目录 前言 漏洞介绍 漏洞原理 产生条件 漏洞影响 防御措施 复现过程 结语 前言 在网络安全的复杂生态中&#xff0c;系统漏洞的探索与防范始终是保障数字世界安全稳定运行的关键所在。Linux Samba 作为一款在网络共享服务领域应用极为广泛的软件&#xff0c;其低版本中…