延迟任务的实现总结

实现延迟任务的方式有很多,各有利弊,有单机和分布式的。在这里做一个总结,在遇到这类问题的时候希望给大家一个参考和思路。

延迟任务有别于定式任务,定式任务往往是固定周期的,有明确的触发时间。而延迟任务一般没有固定的开始时间,它常常是由一个事件触发的,而在这个事件触发之后的一段时间内触发另一个事件。延迟任务相关的业务场景如下:

场景一:物联网系统经常会遇到向终端下发命令,如果命令一段时间没有应答,就需要设置成超时。

场景二:订单下单之后30分钟后,如果用户没有付钱,则系统自动取消订单。

下面我们来探讨一些方案,其实这些方案没有好坏之分,和系统架构一样,只有最适合。对于数据量较小的情况下,任意一种方案都可行,考虑的是简单明了和开发速度,尽量避免把系统搞复杂了。而对于数据量较大的情况下,就需要有一些选择,并不是所有的方案都适合了。

 

1. 数据库轮询

这是比较常见的一种方式,所有的订单或者所有的命令一般都会存储在数据库中。我们会起一个线程去扫数据库或者一个数据库定时Job,找到那些超时的数据,直接更新状态,或者拿出来执行一些操作。这种方式很简单,不会引入其他的技术,开发周期短。

如果数据量比较大,千万级甚至更多,插入频率很高的话,上面的方式在性能上会出现一些问题,查找和更新对会占用很多时间,轮询频率高的话甚至会影响数据入库。一种可以尝试的方式就是使用类似TBSchedule或Elastic-Job这样的分布式的任务调度加上数据分片功能,把需要判断的数据分到不同的机器上执行。

如果数据量进一步增大,那扫数据库肯定就不行了。另一方面,对于订单这类数据,我们也许会遇到分库分表,那上述方案就会变得过于复杂,得不偿失。

 

2. JDK延迟队列

Java中的DelayQueue位于java.util.concurrent包下,作为单机实现,它很好的实现了延迟一段时间后触发事件的需求。由于是线程安全的它可以有多个消费者和多个生产者,从而在某些情况下可以提升性能。DelayQueue本质是封装了一个PriorityQueue,使之线程安全,加上Delay功能,也就是说,消费者线程只能在队列中的消息“过期”之后才能返回数据获取到消息,不然只能获取到null。

之所以要用到PriorityQueue,主要是需要排序。也许后插入的消息需要比队列中的其他消息提前触发,那么这个后插入的消息就需要最先被消费者获取,这就需要排序功能。PriorityQueue内部使用最小堆来实现排序队列。队首的,最先被消费者拿到的就是最小的那个。使用最小堆让队列在数据量较大的时候比较有优势。使用最小堆来实现优先级队列主要是因为最小堆在插入和获取时,时间复杂度相对都比较好,都是O(logN)。

下面例子实现了未来某个时间要触发的消息。我把这些消息放在DelayQueue中,当消息的触发时间到,消费者就能拿到消息,并且消费,实现处理方法。示例代码:

 

/*

 * 定义放在延迟队列中的对象,需要实现Delayed接口
 
*/

public class DelayedTask implements Delayed {
    private int _expireInSecond = 0;

    public DelayedTask(int delaySecond) {
        Calendar cal = Calendar.getInstance();
        cal.add(Calendar.SECOND, delaySecond);
        _expireInSecond = (int) (cal.getTimeInMillis() / 1000);
    }

    public int compareTo(Delayed o) {
        long d = (getDelay(TimeUnit.NANOSECONDS) - o.getDelay(TimeUnit.NANOSECONDS));
        return (d == 0) ? 0 : ((d < 0) ? -1 : 1);
    }

    public long getDelay(TimeUnit unit) {       
	//TODO Auto-generated method stub   
        Calendar cal = Calendar.getInstance();
        return _expireInSecond - (cal.getTimeInMillis() / 1000); 
    }

}

 

下面定义了三个延迟任务,分别是10秒,5秒和15秒。依次入队列,期望5秒钟后,5秒的消息先被获取到,然后每个5秒钟,依次获取到10秒数据和15秒的那个数据。

public static void main(String[] args) throws InterruptedException {
        
	//TODO Auto-generated method stub
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

        //定义延迟队列
        DelayQueue<DelayedTask> delayQueue = new DelayQueue<DelayedTask>();

        //定义三个延迟任务
        DelayedTask task1 = new DelayedTask(10);
       	DelayedTask task2 = new DelayedTask(5);
        DelayedTask task3 = new DelayedTask(15);

        delayQueue.add(task1);
        delayQueue.add(task2);
        delayQueue.add(task3);

        System.out.println(sdf.format(new Date()) + " start");
        
       	while(delayQueue.size() != 0) {
            
            //如果没到时间,该方法会返回

            DelayedTask task = delayQueue.poll();  
    	    if(task != null) {
                Date now = new Date();
                System.out.println(sdf.format(now));
            }
            
            Thread.sleep(1000);
        }
    }

 

输出结果如下图:

 

DelayQueue是一种很好的实现方式,虽然是单机,但是可以多线程生产和消费,提高效率。拿到消息后也可以使用异步线程去执行下一步的任务。如果有分布式的需求可以使用Redis来实现消息的分发,如果对消息的可靠性有非常高的要求可以使用消息中间件:

image

 

使用DelayQueue需要考虑程序挂掉之后,内存里面未处理消息的丢失带来的影响。

3. JDK ScheduledExecutorService

JDK自带的一种线程池,它能调度一些命令在一段时间之后执行,或者周期性的执行。文章开头的一些业务场景主要使用第一种方式,即,在一段时间之后执行某个操作。代码例子如下:

public static void main(String[] args) {
        // TODO Auto-generated method stub

        ScheduledExecutorService executor = Executors.newScheduledThreadPool(100);

        for(inti = 10; i > 0; i--) {
            executor.schedule(new Runnable() {
		public void run() {
                    //TODO Auto-generated method stub

                    System.out.println("Work start, thread id:" + Thread.currentThread().getId() + " " + sdf.format(new Date()));
                }
            }, i, TimeUnit.SECONDS);
        }
    }

执行结果:

 

ScheduledExecutorService的实现类ScheduledThreadPoolExecutor提供了一种并行处理的模型,简化了线程的调度。DelayedWorkQueue是类似DelayQueue的实现,也是基于最小堆的、线程安全的数据结构,所以会有上例排序后输出的结果。

ScheduledExecutorService比上面一种DelayQueue更加实用。因为,一般来说,使用DelayQueue获取消息后触发事件都会实用多线程的方式执行,以保证其他事件能准时进行。而ScheduledThreadPoolExecutor就是对这个过程进行了封装,让大家更加方便的使用。同时在加强了部分功能,比如定时触发命令。

 

4. 时间轮

时间轮是一种非常惊艳的数据结构。其在Linux内核中使用广泛,是Linux内核定时器的实现方法和基础之一。按使用场景,大致可以分为两种时间轮:原始时间轮和分层时间轮。分层时间轮是原始时间轮的升级版本,来应对时间“槽”数量比较大的情况,对内存和精度都有很高要求的情况。我们延迟任务的场景一般只需要用到原始时间轮就可以了。

原始时间轮:如下图一个轮子,有8个“槽”,可以代表未来的一个时间。如果以秒为单位,中间的指针每隔一秒钟转动到新的“槽”上面,就好像手表一样。如果当前指针指在1上面,我有一个任务需要4秒以后执行,那么这个执行的线程回调或者消息将会被放在5上。那如果需要在20秒之后执行怎么办,由于这个环形结构槽数只到8,如果要20秒,指针需要多转2圈。位置是在2圈之后的5上面(20 % 8 + 1)。这个圈数需要记录在槽中的数据结构里面。这个数据结构最重要的是两个指针,一个是触发任务的函数指针,另外一个是触发的总第几圈数。时间轮可以用简单的数组或者是环形链表来实现。

原始的时间轮

 

相比DelayQueue的数据结构,时间轮在算法复杂度上有一定优势。DelayQueue由于涉及到排序,需要调堆,插入和移除的复杂度是O(lgn),而时间轮在插入和移除的复杂度都是O(1)。

时间轮比较好的开源实现是Netty的

//创建Timer, 精度为100毫秒,

        HashedWheelTimer timer = new HashedWheelTimer();

        System.out.println(sdf.format(new Date()));

        MyTask task1 = new MyTask();
        MyTask task2 = new MyTask();
        MyTask task3 = new MyTask();
        
        timer.newTimeout(task1, 5, TimeUnit.SECONDS);
        timer.newTimeout(task2, 10, TimeUnit.SECONDS);
        timer.newTimeout(task3, 15, TimeUnit.SECONDS);
        
        //阻塞main线程

        try{
            System.in.read();
        } catch(IOException e) {
            //TODO Auto-generated catch block
            e.printStackTrace();
        }

其中HashedWheelTimer有多个构造函数。其中:

ThreadFactory :创建线程的类,默认Executors.defaultThreadFactory()。

TickDuration:多少时间指针顺时针转一格,单位由下面一个参数提供。

TimeUnit:上一个参数的时间单位。

TicksPerWheel:时间轮上的格子数。

如果一个任务要在120s后执行,时间轮是默认参数的话,那么这个任务在时间轮上需要经过

120000ms / (512 * 100ms) = 2轮

120000ms % (512 * 100ms) = 176格。

在使用HashedWheelTimer的过程中,延迟任务的实现最好使用异步的,HashedWheelTimer的任务管理和执行都在一个线程里面。如果任务比较耗时,那么指针就会延迟,导致整个任务就会延迟。

 

4. Quartz

quartz是一个企业级的开源的任务调度框架,quartz内部使用TreeSet来保存Trigger,如下图。Java中的TreeSet是使用TreeMap实现,TreeMap是一个红黑树实现。红黑树的插入和删除复杂度都是logN。和最小堆相比各有千秋。最小堆插入比红黑树快,删除顶层节点比红黑树慢。

 

相比上述的三种轻量级的实现功能丰富很多。有专门的任务调度线程,和任务执行线程池。quartz功能强大,主要是用来执行周期性的任务,当然也可以用来实现延迟任务。但是如果只是实现一个简单的基于内存的延时任务的话,quartz就稍显庞大。

 

5. Redis ZSet

Redis中的ZSet是一个有序的Set,内部使用HashMap和跳表(SkipList)来保证数据的存储和有序,HashMap里放的是成员到score的映射,而跳跃表里存放的是所有的成员,排序依据是HashMap里存的score,使用跳跃表的结构可以获得比较高的查找效率,并且在实现上比较简单。

 

public class ZSetTest {

    private JedisPool jedisPool = null;
    //Redis服务器IP

    private String ADDR = "10.23.22.42";
    //Redis的端口号

    private int PORT = 6379;

    private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public void intJedis() {
        jedisPool = new JedisPool(ADDR, PORT);
    }

    public static void main(String[] args) {
        //TODO Auto-generated method stub

        ZSetTest zsetTest = new ZSetTest();
        zsetTest.intJedis();

        zsetTest.addItem();
        zsetTest.getItem();

        zsetTest.deleteZSet();
    }

    public void deleteZSet() {
        Jedis jedis = jedisPool.getResource();
        jedis.del("zset_test");
    }

    public void addItem() {
        Jedis jedis = jedisPool.getResource();

        Calendar cal1 = Calendar.getInstance();
        cal1.add(Calendar.SECOND, 10);
        int second10later = (int) (cal1.getTimeInMillis() / 1000);

        Calendar cal2 = Calendar.getInstance();
        cal2.add(Calendar.SECOND, 20);
        int second20later = (int) (cal2.getTimeInMillis() / 1000);

        Calendar cal3 = Calendar.getInstance();
        cal3.add(Calendar.SECOND, 30);
        int second30later = (int) (cal3.getTimeInMillis() / 1000);

        Calendar cal4 = Calendar.getInstance();
        cal4.add(Calendar.SECOND, 40);
        int second40later = (int) (cal4.getTimeInMillis() / 1000);

        Calendar cal5 = Calendar.getInstance();
        cal5.add(Calendar.SECOND, 50);
        int second50later = (int) (cal5.getTimeInMillis() / 1000);

        jedis.zadd("zset_test", second50later, "e");
        jedis.zadd("zset_test", second10later, "a");
        jedis.zadd("zset_test", second30later, "c");
        jedis.zadd("zset_test", second20later, "b");
        jedis.zadd("zset_test", second40later, "d");

        System.out.println(sdf.format(new Date()) + " add finished.");
    }

    public void getItem() {

        Jedis jedis = jedisPool.getResource();

        while(true) {
            try {

                Set<Tuple> set = jedis.zrangeWithScores("zset_test", 0, 0);

                String value = ((Tuple) set.toArray()[0]).getElement();
                int score = (int) ((Tuple) set.toArray()[0]).getScore();

                Calendar cal = Calendar.getInstance();
                int nowSecond = (int) (cal.getTimeInMillis() / 1000);

                if (nowSecond >= score) {
                    jedis.zrem("zset_test", value);
                    System.out.println(sdf.format(new Date()) + " removed value:" + value);
                }

                if(jedis.zcard("zset_test") <= 0)
                {
                    System.out.println(sdf.format(new Date()) + " zset empty ");
                    return;
                }
                Thread.sleep(1000);
            } catch(InterruptedException e) {
                //TODO Auto-generated catch block

                e.printStackTrace();
            }
        }
    }

}

 

在用作延迟任务的时候,可以在添加数据的时候,使用zadd把score写成未来某个时刻的unix时间戳。消费者使用zrangeWithScores获取优先级最高的(最早开始的的)任务。注意,zrangeWithScores并不是取出来,只是看一下并不删除,类似于Queue的peek方法。程序对最早的这个消息进行验证,是否到达要运行的时间,如果是则执行,然后删除zset中的数据。如果不是,则继续等待。

由于zrangeWithScores 和 zrem是先后使用,所以有可能有并发问题,即两个线程或者两个进程都会拿到一样的一样的数据,然后重复执行,最后又都会删除。如果是单机多线程执行,或者分布式环境下,可以使用Redis事务,也可以使用由Redis实现的分布式锁,或者使用下例中Redis Script。你可以在Redis官方的Transaction 章节找到事务的相关内容。

使用Redis的好处主要是:

1. 解耦:把任务、任务发起者、任务执行者的三者分开,逻辑更加清晰,程序强壮性提升,有利于任务发起者和执行者各自迭代,适合多人协作。

2. 异常恢复:由于使用Redis作为消息通道,消息都存储在Redis中。如果发送程序或者任务处理程序挂了,重启之后,还有重新处理数据的可能性。

3. 分布式:如果数据量较大,程序执行时间比较长,我们可以针对任务发起者和任务执行者进行分布式部署。特别注意任务的执行者,也就是Redis的接收方需要考虑分布式锁的问题。

 

6. Jesque

Jesque是Resque的java实现,Resque是一个基于Redis的Ruby项目,用于后台的定时任务。Jesque实现延迟任务的方法也是在Redis里面建立一个ZSet,和上例一样的处理方式。上例提到在使用ZSet作为优先级队列的时候,由于zrangeWithScores 和 zrem没法保证原子性,所有在分布式环境下会有问题。在Jesque中,它使用的Redis Script来解决这个问题。Redis Script可以保证操作的原子性,相比事务也减少了一些网络开销,性能更加出色。

 

7. RabbitMQ TTL和DXL

使用RabbitMQ的TTL和DXL实现延迟队列在这里不做详细的介绍。

 

综上所述,解决延迟队列有很多种方法。选择哪个解决方案也需要根据不同的数据量、实时性要求、已有架构和组件等因素进行判断和取舍。对于比较简单的系统,可以使用数据库轮训的方式。数据量稍大,实时性稍高一点的系统可以使用JDK延迟队列(也许需要解决程序挂了,内存中未处理任务丢失的情况)。如果需要分布式横向扩展的话推荐使用Redis的方案。但是对于系统中已有RabbitMQ,那RabbitMQ会是一个更好的方案。

  • 4
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
作者:杨红超 绪论 本章主要阐述多功能气压计的应用背景,包括根据大气压强判断和确定建筑工人的楼层位置、帮助建筑工人感知周围环境温度变化等,同时也可根据大气压强判断无人机的GPS定位。其次着重介绍气压计的具体功能,如可以通过对工人周围的气压和温度的采集计算出当前所在的楼层位置,并将数据信息通过ONENET上传到云服务器上,通过语音识别实现设备在线升级功能。最后对本次设计的组织结构进行介绍,以表明每章节的主要内容和作用。 1.1应用背景 为确保建筑工人在工地施工时的人身安全,故此设计一款工人们的“运行手环”——多功能气压计。它内置气体压强检测装置可实时检测建筑工人身处的大气压强和周围的环境温度,进而根据采集的数据推算出工人所在的楼层高度,并将数据实时上传到云服务器上通过管理者对数据的检测给建筑工人提供一份双保险。 此外,该多功能气压计还具有无人机GPS定位和判断吸尘器吸力大小等应用于多对象、多环境的特点。 1.2实现功能 本次多功能气压计的设计硬件采用ART-Pi开发板、LPS22HH气压传感器和LD3320语音识别模块,操作系统使用RT-Thread 4.0.3,软件使用RT-Thread Studio 1.1.5开发平台和使用C语言实现软件编程,具有如下功能: (1)利用LPS22HH气压传感器实时对大气压的压强进行采集,并通过SPI4接口将采集的数据传给单片机。 (2)利用AP6212 WIFI模块实时将经过处理后的数据,如温度值和楼层信息等上传到云服务器;同时通过WIFI模块实现在线升级用户程序。 (3)语音识别模块通过SPI2实现与开发板的数据交互,完成语音重启设备和语音在线升级功能。 1.3设计组织结构 本次基于ART-Pi开发板的多功能气压计设计,主要分为五个章节其具体设计组织结构如下: 第一章绪论,主要介绍多功能气压计的实际应用背景和具有的具体功能,以及设计组织结构的规划。 第二章RT-Thread概述,主要对在本设计中涉及的RT-Thread内核、及其组件和软件包进行阐述,欲以说明对RT-Thread操作系统的使用情况和了解程度,同时对其内容作些简单的介绍。 第三章硬件设计,主要阐述硬件模块的电气连接和本次设计使用的硬件电路,如LPS22HH气压传感器、ART-Pi开发板和语音识别模块等。 第四章软件设计,主要阐述软件实现的设计流程和各个软件模块设计的框架,以及模块之间的通讯方式。 第五章总结与展望,主要阐述对本次设计的评估,即软件设计存在的不足和功能与性能存在的不足,以及针对不足之处提出的具体改进措施和方案、参加比赛的感悟和心得。 1.4本章小节 本章主要介绍多功能气压计的应用背景和具体的应用功能,以及对软硬件开发环境和该设计文档的组织结构进行阐述。 RT-Thread概述 本章主要阐述在软件设计中关于RT-Thread操作系统的应用情况,如与线程运行有关的采用动态方式创建线程,与线程同步有关的信号量的动态创建、释放和获取,以及与网络有关的SAL组件、OneNET软件包等。 2.1内核 为了提高软件运行的并发性和数据采集的实时性,故使用内核中的核心部分——线程,使其维护和管理每个任务的运行,同时以使用信号量和事件集等的同步方式,以及使用邮箱和消息队列等的通信方式来确保每个任务在运行过程中能“自由飞翔”。 不仅如此,为增加任务在运行状态中的时间和提高每个任务响应的快慢速度,故使用线程中重要的两个属性即线程优先级和时间片,并根据应用的具体环境和软实时性的要求将每个任务赋予各自该具有的任务优先级和时间片。同时,使用rt_thread_mdelay延迟函数来定时释放线程(任务)确保在其处于非运行态时可以退出时间片以让其它任务能及时运行。 2.2驱动 在bootloader程序中主要初始化SPI和SFUD驱动实现对外部SPI FLASH的读写操作,同时结合使用FAL软件包将操作FLASH的函数进行分层,进而实现在bootloader程序中读取“download”分区的升级固件,以及使用ota_downloader软件包完成升级固件的下载,最后使用出厂W25QXX函数实现固件搬运工作进而完成在线升级应用。 2.3组件 SAL组件完成并提供了基于RT-Thread操作系统的对不同网络协议栈或网络实现接口的抽象和与上层应用有关的一组标准BSD Socket API,在网络开发设计时可只需关心和使用网络应用层提供的网络接口,而无需关心底层具体网络协议栈类型和具体实现,如TCP/IP协议栈、LWIP和AT Socket网络。SAL组件不仅提高了软件系统对底层驱动的兼容性,而且缩短了网络开发周期。此外,SAL组件还具有一下功能特点[1]: 抽象、统一多种网络协议栈接口; 提供Socket层面的TLS加密传输特性; 支持标准BSP S
python入门到高级全栈工程师培训视频学习资料;本资料仅用于学习,请查看后24小时之内删除。 【课程内容】 第1章 01 计算机发展史 02 计算机系统 03 小结 04 数据的概念 05 进制转换 06 原码补码反码 07 物理层和数据链路层 08 网络层和arp协议 09 传输层和应用层 第2章 01 上节课复习 02 arp协议复习 03 字符编码 第3章 01 网络基础和dos命令 02 为何学习linux 03 课程内容介绍 04 操作系统内核与系统调用 05 操作系统安装原理 06 linux操作系统安装 07 初识linux命令 08 linux操作系统目录结构 09 目录及文件操作 第4章 01 上节课复习 02 创建用户相关的文件 03 用户增删该查及组相关操作 04 对文件的权限管理 05 对目录的权限管理 06 权限管理补充 07 属主属组及基于数字的权限管理 第5章 01 上节课复习 02 文件合并与文件归档 03 文件归档与两种压缩方式 04 vim编辑器 05 系统启动流程 06 grub加密 07 bios加密 08 top命令 09 free命令 10 进程管理 第6章 01 上节课复习 02 磁盘分区 03 文件系统与挂载 04 挂载信息讲解 05 磁盘用满的两种情况 06 软连接和硬链接 07 软连接和硬链接补充 第7章 01 ip地址与子网划分 02 ip地址配置 03 虚拟机网络模式 04 三层隔离验证试验 第8章 01 上节课复习 02 软件包介绍 03 rpm软件包管理 04 yum软件包管理 05 源码安装python3.5 06 ssh服务 07 apache服务 08 samba服务 第9章 01 Python开发系列课程概要 02 Python作业要求以及博客 03 编程语言介绍 04 Python种类介绍 05 Python安装以及环境变量的操作 06 Python初识以及变量 07 Python条件语句和基本数据类型 08 Python while循环语句以及练习题 09 练习题讲解 第10章 01 上节内容回顾以及补充 02 上周作业实现 03 Pycharm的安装和使用 04 Python 运算符 05 Python 运算符以及总结 06 Python 基本数据类型介绍 07 Python 整形的魔法 08 Python 字符串的魔法 第11章 01 Python 字符串的魔法 02 Python range的用法以及练习 03 Python 课上练习解释 04 Python 基础知识练习题试题 第12章 01 今日内容介绍以及基础测试题答案讲解 02 Python 列表的魔法 03 Python 元组的魔法 04 Python 字典的魔法 05 Python 错误更正:布尔值可以作为字典的key 06 Python 今日内容整理 第13章 第13章共1课 第14章 01 数据类型和变量总结 02 集合定义和基本操作方法 03 集合关系运算交,差,并集 04 集合的其他内置方法 05 集合补充 06 百分号字符串拼接 07 format字符串格式化 08 数学意义的函数与python中的函数 09 为何要有函数 10 函数返回值 11 可变长参数 第15章 01 上节课复习 02 全局变量与局部变量 03 风湿理论之函数即变量 04 函数递归 05 函数递归补充 第16章 01 上节课回顾 02 函数作用域 03 函数作用域补充 04 匿名函数 05 函数式编程介绍 06 函数式编程尾递归调用优化 07 map函数 08 map函数filter函数 09 reduce函数 10 map reduce filter总结 11 内置函数part1 第17章 01 课前吹牛 02 zip方法 03 max和min高级使用 04 其他内置函数 05 文件操作的其他模式 第18章 01 上节课复习 02 文件处理b模式 03 文件操作的其他方法 04 文件seek方法补充 05 迭代器协议和for循环工作机制 06 迭代器补充 07 三元运算,列表解析,生成器表达式 第19章 01 生成器函数 02 生成器函数的好处 03 母鸡下蛋的传说 04 生成器特性阐释 05 生产者消费者模型 06 第三次作业讲解 第20章 01 上节课回顾 02 装饰器基本理论 03 高阶函数使用 04 函数闭包 05 函数闭包装饰器基本实现 06 函数闭包加上返回值 07 函数闭包加上参数 08 函数闭包补充:解压序列 09 函数闭包为函数加上认证功能 10 函数闭包模拟session 11 函数闭包装饰器运行流程 12 函数闭包带参数装饰器 第21章 01 查询功能 02 修改功能 03 程序的解耦 04 module模块和包的介绍 05 模块的执行以及__name__ 06 关于模块的介绍 07 time时间模块 08 random模块 第22章 01 模块的补充 02 sys修改环境变量 03 BASEDIR的介绍 04 os模块的介绍 05 sys模块的介绍 06 json模块 07 pickle模块 08 shelve模块 09 XML模块 10 re模块简介 11 re模块之元字符 第23章 01 re模块之转义字符 02 re模块之分组 03 re模块之方法 04 re模块总结 05 logging模块 06 re模块补充 07 configparse模块 08 hashlib模块 09 计算器作业以及思路 10 模块导入补充 第24章 01 面向对象设计 02 类相关知识 03 对象相关知识 04 类属性增删改查 05 实例属性的增删改查 06 对象与实例属性 07 对象与实例属性补充 08 面向对象作业 第25章 01 上节课回顾 02 静态属性 03 类方法 04 静态方法 05 小结 06 组合 07 继承 08 接口继承与归一化设计 09 继承顺序之mro线性顺序列表 10 在python2中的继承顺序是什么 11 在子类中调用父类方法 12 super调用父类的方法 13 选择系统作业讲解 第26章 01 学生自主复习 02 分享列表 03 多态 04 封装 05 面向对象概念总结 06 反射 07 反射及动态导入模块 08 类的内置attr属性 09 类内置attr属性补充 10 继承的方式完成包装 11 组合的方式完成授权 第27章 01 os模块复习 02 上节课复习 03 内置函数补充及getattribute 04 getattribue补充 05 item系列 06 str与repr 07 自定制format 08 slots属性 09 doc属性 10 module和class 11 析构方法 12 call方法 13 迭代器协议 14 迭代器协议实现斐波那契数列 16 描述符答疑 17 描述符优先级 18 软件开发规范 19 pycharm干的好事 第28章 01 上节课复习 02 上下文管理协议 04 异常的构成简单了解 05 描述符应用 08 类的装饰器的基本原理 09 类的装饰器增强版 10 类的装饰器的应用 11 自定制property 12 自定制property流程分析 13 自定制property实现延迟计算功能 14 property补充 15 元类介绍 16 自定义元类 17 函数复习 18 文件操作复习 第29章 01 上节课复习 02 什么是异常处理及异常处理的两种方式对比 03 多分支与万能异常 04 异常处理的其他内容 05 什么时候用异常处理 06 什么是socket 07 套接字发展及分类 08 基于tcp协议的套接字编程 09 socket底层工作原理解释 10 tcp三次握手与四次挥手 第30章 01 上节课复习 02 客户端服务端循环收发消息 03 socket收发消息原理剖析 04 服务端循环链接请求来收发消息 05 补充 06 udp套接字 07 recv与recvfrom的区别及基于udp实现ntp服务 08 基于tcp实现远程执行命令 09 基于tcp实现远程执行命令测试结果 10 粘包现象 11 粘包解决方法 第31章 01 上节课复习 02 socketserver实现并发 03 socketserver模块介绍 04 socketserver源码分析tcp版本 05 socketserver源码分析udp版 06 ftp作业要求讲解 07 补充:认证客户端链接合法性 第32章 01 FTP之参数解析与命令分发 02 FTP之逻辑梳理 03 FTP之验证功能 05 FTP之文件上传 06 FTP之断点续传 08 FTP之进度条 09 FTP之cd切换 11 FTP之创建文件夹及MD5校验思路 第33章 01 操作系统历史 02 进程的概念 03 线程的概念 04 线程的调用以及join方法 05 setDaemon方法和继承式调用.baiduyun.downloading 05 setDaemon方法和继承式调用 第34章 01 上节知识回顾 02 并发并行与同步异步的概念 03 GIL的概念 04 同步锁 05 递归锁 06 同步对象event 07 信号量 08 线程队列 09 生产者消费者模型 10 多进程的调用 第35章 01 进程通信 02 进程池 03 协程 04 事件驱动模型 05 IO模型前戏 06 阻塞IO与非阻塞IO 07 select及触发方式 08 select监听多连接 09 select与epoll的实现区别 第36章 01 异步IO 02 selectors模块介绍 03 selectors模块应用 04 作业介绍 第37章 01 selctors实现文件上传与下载 02 html的介绍 03 html文档树的概念 04 meta标签以及一些基本标签 05 img标签和列表标签 06 form表单之input标签 07 通过form向server端发送数据 08 form表单之select标签 09 table标签 第38章 01 css的四种引入方式 02 css的四种基本选择器 03 css的组合选择器 04 css的属性选择器 05 css的伪类 06 css的选择器优先级 07 css的背景属性 第39章 01 css的文本属性与边框属性 02 css的列表属性与display属性 03 css的内外边距 04 css的内外边距补充 05 css的float属性 06 css的清除浮动 07 css的定位 08 css的margin定位 第40章 01 抽屉作业之head区域(导航条) 02 抽屉作业之置顶区域 03 抽屉作业之content部分 05 抽屉作业之页码部分 06 抽屉作业之footer部分 第41章 01 JS的历史以及引入方式 02 JS的基础规范 03 JS的基本数据类型 04 JS的运算符 05 JS的控制语句与循环 06 JS的循环与异常 07 JS的字符串对象 08 JS的数组对象 09 JS的函数对象 第42章 01 JS的函数作用域 02 JS的window对象之定时器 03 JS的history对象和location对象 04 JS的DOM节点 05 JS的DOM节点 第43章 01 上节知识回顾 02 js之onsubmit事件与组织事件外延 03 DOM节点的增删改查与属性设值 04 正反选练习 05 js练习之二级联动 06 jquery以及jquery对象介绍 07 jquery选择器 08 jquery的查找筛选器 09 jquery练习之左侧菜单 第44章 01 jquery属性操作之html,text,val方法 02 jquery循环方法和attr,prop方法 03 jquery模态对话框与clone的应用 04 jqueryCSS操作之offsets,position以及scrolltop 05 jquery事件绑定与事件委托 06 jquery动画效果 07 jquery扩展与插件 08 jquery扩展补充 09 本周作业轮播图以及思路 第45章 轮播图片css部分 轮播图片js部分 第46章 01 数据库与dbms的概念 02 sql规范 03 数据库操作DDL 04 python s3 day46 mysql的数据类型 05 数据表操作 06 表记录之增删改操作 07 表记录查询之查询 第47章 01 多表查询之连接查询 02 级联删除与set null 03 多表查询之连接查询 04 多表查询之复合查询与子查询 05 mysql之索引 第48章 01 python操作数据库pymysql 02 数据库之事务 03 mysql事务之savepoint 第49章 01 http协议之请求协议 02 http协议之响应协议 03 web框架的概念 04 做一个最简答web框架 05 MVC模式和MTV模式 06 django的一个简单应用 07 django静态文件之static 08 django的url控制系统 09 django的urlConf补充 第50章 01 django之视图函数的介绍 02 django视图之redirec 03 django模板之变量 04 django模板之过滤器 05 django模板之控制语句if和for循环 06 django模板之标签tag补充 07 django模板之自定义filter和simple_tag 08 django模板之继承标签extend和添加标签include 第51章 01 数据库表与表之间的一对多多对多的关系 02 Django的ORM的概念 03 ORM对单表的增删改操作 04 ORM查询API 05 模糊查询之万能的双下换线 第52章 01 上节知识回顾 02 ORM多表操作之一对多增加记录 03 ORM多表操作之一对多查询之对象查询 04 ORM多表操作之一对多查询之双下划线查询 05 ORM多表操作之多对多添加记录 06 ORM多表操作之多对多查询 07 ORM多表操作之F查询与Q查询 08 ORM的querySet集合对象的特性 第53章 01 admin介绍 02 alex首秀失败 03 自定义admin样式 04 admin补充 05 COOKIE介绍 06 COOKIE和SESSION配合使用 第54章 01 今日内容概要 02 Django内容回顾 03 Django请求生命周期之Http请求 04 Django请求生命周期之FBV和CBV 05 Django请求生命周期之CBV扩展 06 瞎扯淡 07 Django请求生命周期之响应内容 08 学员管理示例:数据库设计 09 学员管理示例:班级管理 10 学员管理示例:学员管理 第55章 01 Django的ORM基本操作补充之概要 02 Django的ORM基本操作补充之一对多 03 学员管理示例:编辑学生 04 Django的ORM基本操作补充之多对多 05 学员管理示例:为班级分配老师 06 初识Ajax以及简单应用 07 学员管理示例:Ajax删除学生 08 本节作业以及内容补充 第56章 01 上节内容回顾 02 创建Project以及表结构 03 基于BootStrap和FontAwesome制作页面 04 创建学生信息 05 删除学生信息 第57章 01 上节内容回顾 02 上节bug修复 03 编辑学生信息之前端功能 04 编辑学生信息之后台处理 05 以上内容总结 06 Ajax功能之dataType和traditional 第58章 01 今日内容概要 02 Ajax补充之serialize 03 分页功能介绍 04 分页基础知识 05 Django内置分页 06 扩展Django内置分页 07 自定义分页组件 08 DjangoForm组件初识 第59章 01 Form组件之生成HTML标签 02 Form组件之详解字段 03 Form组件之常用标签示例 04 Form组件之动态绑定数据 第60章 Django序列化共6课 第61章 01 上节内容回顾 02 上传文件 03 制作上传按钮 04 Form组件上传文件 05 上传相关内容梳理 06 Model操作知识提问 07 Model操作概述 08 Model字段 09 Model连表字段参数详解 10 Model自定义多对多第三张表 11 强插一道面试题 12 Model连表操作梳理 13 多对多自关联 14 Model操作补充 15 再插两道JavaScript面试题 16 Model操作之select_related以及prefetch_related 17 Model操作知识梳理以及补充 18 JavaScript两道面试题讲解 第62章 01 今日内容概要 02 创建基本项目 03 XMLHttpRequest对象发送请求 04 XMLHttpRequest对象发送POST请求 05 Iframe伪造Ajax请求 06 Iframe伪造回调函数 07 上述内容整理 08 FormData对象以及Ajax文件上传 09 Iframe文件上传 10 Iframe上传文件 11 JSONP实现AJax跨域 12 内容整理以及CORS简单介绍 第63章 01 项目以及学习介绍 02 企业官网示例功能介绍 03 企业官网示例以及数据库表结构 04 企业官网示例作业要求 05 Toando源码基本基本介绍 第64章 01 组合搜索 02 瀑布流 03 瀑布流作业 第65章 01 今日内容概要 02 瀑布流作业讲解 03 保障系统需求分析 04 保障系统数据库设计 05 保障系统目录结构规定 06 阶段任务安排 第66章 01 保障系统主页功能讲解 02 保障系统主页分类和分页的实现 03 阶段作业:保障系统登录注册 第67章 01 保障系统之登录注册功能讲解 02 保障系统之网站验证码 03 保障系统之一个月免登陆 04 任务安排 第68章 01 保障系统之个人知识库主页 02 保障系统之个人知识库内容筛选 03 保障系统之文章最终页 04 保障系统之KindEditor基本使用 05 保障系统之下节预告 06 今日作业以及下节预告 第69章 01 后台管理功能介绍 02 后台管理页面布局 03 今日作业以及下节预告 第70章 01 后台管理之创建报障单 02 后台管理之处理报障单 03 后台管理之画图流程 04 后台管理之Highchart统计保障单 第71章 01 权限管理要求 02 权限管理数据库表设计 03 填充权限数据 04 作业:获取权限以及菜单信息 05 作业思路讲解 06 权限管理之获取用户权限信息 07 权限管理之获取用户菜单信息 08 权限管理之用户权限挂靠到菜单上 09 权限管理之处理菜单等级关系 第72章 01 上节内容概要以及标记应该显示的菜单 02 权限管理之递归生成多级菜单 03 权限管理之标记当前以及激活菜单 04 权限管理之基本使用 05 权限管理之封装权限组件 06 下节预告 第73章 01 CMDB项目介绍 02 CMDB开发背景 03 CMDB开发目的 04 CMDB资产采集方式之agent 05 CMDB资产采集方式之ssh 06 CMDB资产采集方式之saltstack 07 CMDB资产采集方式之puppet 08 CMDB资产采集方式比较 09 CMDB资产采集内容梳理 10 CMDB资产采集功能实现之agent 11 CMDB资产采集功能实现之ssh 12 CMDB资产采集功能实现之saltstack 13 CMDB资产采集插件开发 14 内容回顾之面向对象继承 15 作业:基于配置文件加载插件 第74章 01 CMDB项目上节作业讲解 02 CMDB项目采集资产数据 03 知识拾遗之线程进程池 04 CMDB项目采集资产之日志记录 05 自定义JSON序列化类型 06 本节作业 第75章 01 基于requests模块汇报资产数据 02 基于requests模块汇报API验证 03 CMDB项目示例之API验证流程 04 CMDB项目之数据库表结构 05 CMDB项目之资产汇报并持久化 06 CMDB项目之持久化资产流程 07 本周作业 第76章 01 CMDB项目CURD组件之配置文件构造 02 CMDB项目CURD组件之神奇的单@符号 03 CMDB项目CURD组件之神奇的双@符号 04 CMDB项目CURD组件之自定义td属性 05 下节内容预习 第77章 01 CMDB项目CURD组件之进入编辑模式 02 CMDB项目CURD组件之全选取消反选和编辑模式 03 CMDB项目CURD组件之内容截图 04 CMDB项目CURD组件之进入编辑模式详细 05 CMDB项目CURD组件之属性中应用神奇的单@符号 06 CMDB项目CURD组件之退出编辑模式 07 CMDB项目CURD组件之更新数据 08 CMDB项目CURD组件之基于jQuery扩展封装组件 09 CMDB项目CURD组件之10分钟搞定页面基本操作 10 CMDB项目CURD组件之分页功能 11 CMDB项目CURD组件之搜索功能介绍 12 CMDB项目总结 第78章 01 剩余项目概览 02 CRM项目需求分析 04 CRM项目需求分析及架构设计 05 CRM项目表结构设计 第79章 01 CRM项目实战-前端页面布局 02 CRM项目实战-登录页面开发 04 CRM项目实战-动态菜单设计 05 CRM项目实战-kingadmin开发设计 06 CRM项目实战-kingadmin自动发现及注册功能开发 07 CRM项目实战-kingadmin model obj list页面开发 08 CRM项目实战-kingadmin 根据list_display配置生成数据列表 09 CRM项目实战-kingadmin 多条件过滤功能开发 第80章 01 课前鸡汤 02 分页功能开发 03 分页功能优化 04 排序功能开发 05 分页 排序 筛选组合使用 06 搜索功能开发 第81章 01 CRM项目实战 - 动态modelform的实现 02 CRM项目实战 - 动态modelform 增加自定义样式 03 CRM项目实战 - 实现任意表的增删改查 04 CRM项目实战 - 只读字段的处理 05 CRM项目实战 - filter_horizontal的实现 第82章 01 CRM项目实战 - kingadmin m2m filter_horizontal优化 02 CRM项目实战 - kingadmin 对象删除功能开发 03 CRM项目实战 - kingadmin+admin+action功能开发 04 CRM项目实战 - csrf+token验证原理 第83章 01 CRM项目-kingadmin批量删除 02 CRM项目-学员报名流程开发 03 CRM项目-本次作业需求 第84章 01 SSO介绍 02 用户自定义认证 03 万能通用权限框架设计 04 万能通用权限框架设计-自定义权限钩子实现 第85章 01 堡垒机项目实战-需求讨论 02 堡垒机项目实战-表结构设计 第86章 01 堡垒机项目实战-用户交互程序开发 02 堡垒机项目实战-通过paramiko记录ssh会话记录 03 堡垒机项目实战-把parmaiko代码嵌入用户交互程序 04 堡垒机项目实战-在数据库里记录用户会话数据 第87章 01 堡垒机项目实战-前端模板的选择 02 堡垒机项目实战-web ssh的使用 03 堡垒机项目实战-批量任务的思路 04 堡垒机项目实战-批量任务的前端页面开发 第88章 01 堡垒机实战-批量命令后端开发 02 堡垒机实战-批量命令前端获取执行结果 03 堡垒机实战-批量文件分发 第89章 01 版本管理工具介绍 02 git基本使用 03 github使用 04 git 分支开发流程 05 restful规范介绍 06 restful api设计指南 第90章 01 rabbitmq 消息安全接收 02 rabbitmq 消息持久化 03 rabbitmq 消息订阅发布 04 rabbitmq 消息组播 05 rabbitmq 消息RPC 第91章 01 阶段课程安排介绍 02 爬虫介绍 03 初识爬虫之采集汽车资讯信息 04 requests和beautfulsoup模块基本使用 05 示例:自动登录抽屉新热榜 06 requests模块详细介绍 第92章 01 BeautifulSoup模块详细介绍 02 示例:自动登录知乎 03 示例:自动登录博客园 04 作业之开发Web微信 第93章 01 开发Web微信前戏 02 Web微信流程介绍 03 Web微信之用户扫码 04 Web微信之用户信息初始化 05 Web微信开发总结 第94章 01 Web微信之获取用户初始化信息并展示 02 Web微信之发送微信消息 03 Web微信之获取用户消息 04 Web微信开发总结 第95章 01 多线程实现并发请求 02 多进程实现并发请求 03 异步IO模块的使用 04 自定义异步IO模块前戏 05 自定义异步IO模块开发 06 自定义异步IO模块开发 第96章 01 Scrapy爬虫介绍 02 抽屉示例:初识Scrapy并获取新闻标题 03 抽屉示例:递归获取所有页码 04 抽屉示例:基于pipeline实现标题和URL持久化 第97章 01 Scrapy上节回顾 02 去除重复URL 03 pipeline补充 04 自动登录抽屉并点赞 05 scrapy框架扩展 06 配置文件 第98章 01 Scrapy配置之自动限速以及缓存 02 Scrapy之默认代理以及扩展代理 03 Scrapy之自定义Https证书 04 Scrapy配置之下载中间件 05 Scrapy配置之爬虫中间件 06 Scrapy配置之自定义scrapy命令 07 Scrapy源码流程简述 第99章 01 今日内容概要 02 Scrapy源码剖析前戏之Twisted使用 03 Scrapy源码剖析之自定义Low版框架 04 Scrapy源码剖析之自定义TinyScrapy框架 05 Scrapy源码剖析流程解析 第100章 01 Tornado学习概要 02 Tornado基本操作 03 Tornado自定义Session知识前戏 04 Tornado自定义Session 第101章 01 WebSocket介绍 02 WebSocket握手过程分析 03 基于Python实现WebSocket握手过程 04 位运算补充 05 WebSocket数据解析过程 06 基于Tornado的WebSocket实现聊天室 第102章 01 今日内容复习目标 02 异步非阻塞框架介绍 03 Tornado异步非阻塞功能使用 04 自定义Web框架(同步) 05 自定义Web框架支持同步和异步非阻塞 06 复习计划 第103章 01 缓存数据库介绍 02 redis string操作 03 redis hash 操作 04 redis list操作 05 redis 集合操作 06 redis 其他常用命令 07 redis 发布订阅 08 celery介绍和基本使用 09 celery在项目中使用 10 celery定时任务 11 celery在项目中使用 12 celery 在django中实现定时任务 第104章 就业指导 01 简历制作 02 如何面试 03 最后的鸡汤 04 Nginx+uWSGI+Django部署
### 回答1: Kotlin Flow 是一种基于协程的数据流库,可以用于异步数据处理。要实现延迟代码,可以使用 Flow 的 delay 操作符。 例如,以下代码演示了如何使用 Kotlin Flow 实现延迟代码: ```kotlin import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.collect import kotlinx.coroutines.runBlocking fun main() = runBlocking { flow { emit(1) delay(1000) emit(2) delay(1000) emit(3) }.collect { println(it) } } ``` 在这个例子中,我们使用 flow 函数创建了一个流,并在其中使用 delay 函数实现延迟。collect 函数用于收集流中的数据,并在控制台打印出来。 当运行此代码时,它将输出以下内容: ``` 1 2 3 ``` 这表明我们的延迟代码已经成功执行。 ### 回答2: Kotlin Flow是Kotlin协程中的一个库,用于异步流处理。在Kotlin Flow中实现延迟代码的方法有很多。 一种常见的延迟代码的方法是使用`flow`函数和`emit`操作符来创建一个简单的Flow。可以在Flow的代码块中使用`delay`函数来延迟代码的执行时间。例如: ```kotlin import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* fun delayedFlow(): Flow<Int> = flow { for (i in 1..5) { delay(1000) // 延迟1秒 emit(i) // 发射数据 } } fun main() { delayedFlow() .onEach { println(it) } // 打印数据 .launchIn(GlobalScope) // 启动流 } ``` 在上面的示例中,`delayedFlow`函数是一个自定义的Flow,它会延迟1秒后依次发射1到5的整数数据。在`main`函数中,我们使用`onEach`操作符来打印Flow的每个数据,并使用`launchIn`函数启动了一个协程来收集数据。 除了使用`delay`函数,还可以使用`flow`构造器中的其他挂起函数来实现更复杂的延迟逻辑,例如`withTimeout`、`retry`等等,根据具体需求进行灵活的延迟操作。 总结起来,Kotlin Flow提供了丰富的函数和操作符来实现延迟代码的功能,可以根据具体的需求选择合适的方法来实现。以上只是其中一种常见的方式,你可以根据具体的需求来选择适合自己的方法。 ### 回答3: Kotlin Flow 是 Kotlin 协程库中的一个功能强大的异步流处理工具。它允许我们以声明性的方式处理异步数据流,并可以轻松地实现延迟代码的逻辑。 要实现延迟代码,可以使用 Flow 的 `delay` 操作符。`delay` 操作符可以在发射每个元素前添加一个延迟。 首先,我们需要创建一个 Flow 对象,可以使用 `flowOf` 函数来创建一个简单的流。然后,我们可以使用 `delay` 操作符来添加延迟。 例如,下面是一个实现延迟代码的示例: ```kotlin import kotlinx.coroutines.delay import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.collect import kotlinx.coroutines.flow.flow import kotlinx.coroutines.runBlocking fun main() { runBlocking { // 创建一个 Flow 对象,并在每个元素之间添加延迟 val flow = flowOf(1, 2, 3) .delayEach(1000) // 收集 Flow 中的元素 flow.collect { value -> println(value) } } } suspend fun <T> Flow<T>.delayEach(timeMillis: Long): Flow<T> = flow { collect { value -> delay(timeMillis) emit(value) } } ``` 在这个示例中,我们创建了一个带有延迟的 Flow 对象,其中 `1`、`2`、`3` 依次发射,并且每个元素之间延迟 1000 毫秒。然后我们使用 `collect` 函数来收集 Flow 中的元素,并在控制台中打印每个元素。 通过使用 `delay` 操作符,我们可以轻松地实现延迟代码的逻辑,并在流中处理异步任务

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值