python多任务基础知识

多任务,这个东西好,我们人自己要是同时做几件事情那可老费劲了,编程呢,就不不一样了,对他来说老简单了。
多任务
一、线程
1、 线程概念
由于进程是资源拥有者,创建、撤消与切换存在较大的内存开销,因此需要引入
轻型进程
即线程,
进程是资源分配的最小单位,线程是 CPU 调度的最小单位(程序真正执行的时候
调用的是线程).每一个进程中至少有一个线程。
2、 进程和线程之间的关系
3.使用 threading 模块创建线程
导入 threading 模块
执行顺序:
首先程序运行时,程序从上往下走,遇到 main()函数然后开始执行,执行 mian()
函数的函数体时又创建了两个线程我们称之为子线程,程序运行时的线程我们称
之为主线程
然后子线程根据 target=xxx 开始执行指定的函数
(等子线程结束后主线程结束,程序结束了)
(1) .传递参数
给函数传递参数,使用线程的关键字 args=()进行传递参数
(2) .join()方法
join()方法功能:当前线程执行完后其他线程才会继续执行。
(3) .setDaemon() 方法
setDaemon()将当前线程设置成守护线程来守护主线程: -当主线程结束后,守护线程也就结束,不管是否执行完成。
-应用场景:qq 多个聊天窗口,就是守护线程。
注意:需要在子线程开启的时候设置成守护线程,否则无效。
(4) .实例方法
线程对象的一些实例方法,了解即可

  • getName(): 获取线程的名称。
  • setName(): 设置线程的名称。
  • isAlive(): 判断当前线程存活状态。
    (5) .threading 模块提供的方法
    threading.currentThread(): 返回当前的线程变量。
    threading.enumerate(): 返回一个包含正在运行的线程的 list。正在运行指线程启
    动后、结束前,不包括启动前和终止后的线程。
    threading.activeCount():
    返回正在运行的线程数量,与 len(threading.enumerate())有相同的结果。
    4.使用继承方式开启线程
  1. 定义一个类继承 threading.Thread 类。
  2. 复写父类的 run()方法。
    5.线程之间共享全局变量
    6.共享全局变量的问题
    多线程开发的时候共享全局变量会带来资源竞争效果。也就是数据不安全。
    7.同步异步概念
    同步的意思就是协同步调,按预定的先后次序执行。例如你先说完然后我再说。
    大家不要将同步理解成一起动作,同步是指协同、协助、互相配合。
    例如线程同步,可以理解为线程 A 和 B 一块配合工作,A 执行到一定程度时要依
    靠 B 的某个结果,于是停下来示意 B 执行,B 执行完将结果给 A,然后 A 继续执行。
    A 强依赖 B(对方),A 必须等到 B 的回复,才能做出下一步响应。即 A 的操作(行
    程)是顺序执行的,中间少了哪一步都不可以,或者说中间哪一步出错都不可以。
    举个例子:
    你去外地上学(人生地不熟),突然生活费不够了;此时你决定打电话回家,通知
    家里转生活费过来,可是当你拨出电话时,对方一直处于待接听状态(即:打不
    通,联系不上),为了拿到生活费,你就不停的 oncall、等待,最终可能不能及时
    要到生活费,导致你今天要做的事都没有完成,而白白花掉了时间。
    异步:
    异步则相反,A 并不强依赖 B,A 对 B 响应的时间也不敏感,无论 B 返回还是不
    返回,A 都能继续运行;B 响应并返回了,A 就继续做之前的事情,B 没有响应,
    A 就做其他的事情。也就是说 A 不存在等待对方的概念。
    举个例子:
    在你打完电话发现没人接听时,猜想:对方可能在忙,暂时无法接听电话,所以
    你发了一条短信(或者语音留言,亦或是其他的方式)通知对方后便忙其他要紧的
    事了;这时你就不需要持续不断的拨打电话,还可以做其他事情;待一定时间后,
    对方看到你的留言便回复响应你,当然对方可能转钱也可能不转钱。但是整个一
    天下来,你还做了很多事情。 或者说你找室友临时借了一笔钱,又开始 happy
    的上学时光了。
    对于多线程共享全局变量计算错误的问题,我们可以使用线程同步来进行解决。
    8.互斥锁
    当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能
    够保证多个线程安全的访问竞争资源(全局内容),最简单的同步机制就是使用互
    斥锁。
    某个线程要更改共享数据时,先将其锁定,此时资源的状态为锁定状态,其他线
    程就能更改,直到该线程将资源状态改为非锁定状态,也就是释放资源,其他的
    线程才能再次锁定资源。互斥锁保证了每一次只有一个线程进入写入操作。从而
    保证了多线程下数据的安全性。
    1.练习一使用互斥锁解决 200 万次的计算问题。
    9.死锁
    在多个线程共享资源的时候,如果两个线程分别占有一部分资源,并且同时等待
    对方的资源,就会造成死锁现象。
    如果锁之间相互嵌套,就有可能出现死锁。因此尽量不要出现锁之间的嵌套。
    如果双方都不开口,双方就一直等待。
    10.线程队列 Queue
    队列是一种先进先出(FIFO)的存储数据结构,就比如排队上厕所一个道理。
    1.创建一个“队列”对象
    import Queue # 导入模块
    q = Queue.Queue(maxsize = 10)
    Queue.Queue 类即是一个队列的同步实现。队列长度可为无限或者有限。可通过
    Queue 的构造函数的可选参数 maxsize 来设定队列长度。如果 maxsize 小于 1 就
    表示队列长度无限。
    2.将一个值放入队列中 q.put(10)
    调用队列对象的 put()方法在队尾插入一个项目。
  3. 将一个值从队列中取出 q.get()
    从队头删除并返回一个项目。如果取不到数据则一直等待。
    4.q.qsize() 返回队列的大小
    5.q.empty() 如果队列为空,返回 True,反之 False
    6.q.full() 如果队列满了,返回 True,反之 False
    7.q.put_nowait(item) ,如果取不到不等待,之间抛出异常。
    8.q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列
    发送一个信号
    9.q.join() 收到 q.task_done()信号后再往下执行,否则一直等待。或者最开始时没
    有放数据 join()不会阻塞。
    q.task_done() 和 q.join() 通常一起使用。
    11.生产者与消费者模型
    在并发编程中,如果生产者处理速度很快,而消费者处理速度比较慢,那么生产
    者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处
    理能力大于生产者,那么消费者就必须等待生产者。为了解决这个等待的问题,
    就引入了生产者与消费者模型。让它们之间可以不停的生产和消费
    (1)什么是生产者消费者模式
    生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者
    生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要
    数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者
    和消费者的处理能力。
    这就像,在餐厅,厨师做好菜,不需要直接和客户交流,而是交给前台,而客户
    去饭菜也不需要不找厨师,直接去前台领取即可。
    12.GIL 全局解释锁
    GIL 即 :global interpreter lock 全局解释所。
    在进行 GIL 讲解之前,我们可以先了解一下并行和并发:
    并行:多个 CPU 同时执行多个任务,就好像有两个程序,这两个程序是真的在
    两个不同的 CPU 内同时被执行。
    并发:CPU 交替处理多个任务,还是有两个程序,但是只有一个 CPU,会交替处
    理这两个程序,而不是同时执行,只不过因为 CPU 执行的速度过快,而会使得
    人们感到是在“同时”执行,执行的先后取决于各个程序对于时间片资源的争夺. 并行和并发同属于多任务,目的是要提高 CPU 的使用效率。这里需要注意的是,
    一个 CPU 永远不可能实现并行,即一个 CPU 不能同时运行多个程序。
    Guido van Rossum(吉多·范罗苏姆)创建 python 时就只考虑到单核 cpu,解决
    多线程之间数据完整性和状态同步的最简单方法自然就是加锁, 于是有了 GIL
    这把超级大锁。因为 cpython 解析只允许拥有 GIL 全局解析器锁才能运行程序,
    这样就保证了保证同一个时刻只允许一个线程可以使用 cpu。也就是说多线程并
    不是真正意义上的同时执行。
    练习 1:迅雷下载器(使用线程讲解)
    二、协程
    协程:协助程序,线程和进程都是抢占式特点,线程和进程的切换我们是不能参
    与的。
    而协程是非抢占式特点,协程也存在着切换,这种切换是由我们用户来控制的。
    协程主解决的是 IO 的操作。
    协程,又称微线程,纤程。英文名 Coroutine。
    优点 1: 协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身
    控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优
    势就越明显。
    优点 2: 不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,
    在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程
    高很多。
    因为协程是一个线程执行,那怎么利用多核 CPU 呢?最简单的方法是多进程+
    协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。
    1、 yield 的简单实现
    2、 greenlet 模块
    greenlet 是一个用 C 实现的协程模块,相比与 python 自带的 yield,它可以使你
    在任意函数之间随意切换,而不需把这个函数先声明为 generator
    安装 :pip3 install greenlet。如果引入的时候还是报错,使用 pycharm 进行下载
    安装,
    选择 file --> settings --> Project:项目名称 --> Project Interpreter–>…
    greenlet 模块
  • gr1=greenlet(目标函数) - gr1.switch() 切换执行
    代码:
    结果:
    3、 gevent 模块
    下载 gevent 安装包
    练习 1:图片下载器
    三、IO 模型
    传统的编程是如下线性模式的:
    开始—>代码块 A—>代码块 B—>代码块 C—>代码块 D—>…—>结束
    每一个代码块里是完成各种各样事情的代码,但编程者知道代码块 A,B,C,D… 的执行顺序,唯一能够改变这个流程的是数据。输入不同的数据,根据条件
    语句判断,流程或许就改为 A—>C—>E…—>结束。每一次程序运行顺序或许
    都不同,但它的控制流程是由输入数据和你编写的程序决定的。如果你知道
    这个程序当前的运行状态(包括输入数据和程序本身),那你就知道接下来
    甚至一直到结束它的运行流程。
    对于事件驱动型程序模型,它的流程大致如下:
    开始—>初始化—>等待
    与上面传统编程模式不同,事件驱动程序在启动之后,就在那等待,等待什
    么呢?等待被事件触发。传统编程下也有“等待”的时候,比如在代码块 D
    中,你定义了一个 input(),需要用户输入数据。但这与下面的等待不同,传
    统编程的“等待”,比如 input(),你作为程序编写者是知道或者强制用户输
    入某个东西的,或许是数字,或许是文件名称,如果用户输入错误,你还需
    要提醒他,并请他重新输入。事件驱动程序的等待则是完全不知道,也不强
    制用户输入或者干什么。只要某一事件发生,那程序就会做出相应的“反应”。
    这些事件包括:输入信息、鼠标、敲击键盘上某个键还有系统内部定时器触
    发。
    1、 事件驱动模型介绍
    如果直接介绍事件驱动模型大家肯定懵逼,我们现在直接上现象,就是咱们网页
    中的点击一下按钮就触发不同的操作,这就是采用了事件驱动模型的思想。
    事件驱动模型大体思路如下:
    (1).有一个事件(消息)队列;
    (2).鼠标按下时,往这个队列中增加一个点击事件(消息);
    (3).有个循环,不断从队列取出事件,根据不同的事件,调用不同的函数,如
    onClick()、
    onKeyDown()等;
    注意,事件驱动的监听事件是由操作系统调用的 cpu 来完成的
    (4).事件(消息)一般都各自保存各自的处理函数指针,这样,
    每个消息都有独立的处理函数;
    事件驱动编程是一种编程范式,这里程序的执行流由外部事件来决定。它的特点
    是包含一个事件循环,当外部事件发生时使用回调机制来触发相应的处理。
    2、 IO 模型前戏准备
    在进行解释之前,首先要说明几个概念:
    1、用户空间和内核空间
    2、进程切换
    3、进程的阻塞
    4、文件描述符
    5、缓存 I/O
    (1)、用户空间与内核空间
    现在操作系统都是采用虚拟存储器,那么对 32 位操作系统而言,它的寻址空间(虚拟存储
    空间)为 4G(2 的 32 次方)。
    操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底
    层硬件设备的所有权限。
    为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分
    为两部分,一部分为内核空间,一部分为用户空间。
    针对 linux 操作系统而言,将最高的 1G 字节(从虚拟地址 0xC0000000 到 0xFFFFFFFF),
    供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),
    供各个进程使用,称为用户空间。
    (2)、进程切换
    为了控制进程的执行,内核必须有能力挂起正在 CPU 上运行的进程,并恢复以前挂起的某
    个进程的执行。这种行为被称为进程切换,这种切换是由操作系统来完成的。因此可以说,
    任何进程都是在操作系统内核的支持下运行的,是与内核紧密相关的。
    从一个进程的运行转到另一个进程上运行,这个过程中经过下面这些变化:
    保存处理机上下文,包括程序计数器和其他寄存器。
    更新 PCB 信息。
    把进程的 PCB 移入相应的队列,如就绪、在某事件阻塞等队列。
    选择另一个进程执行,并更新其 PCB。
    更新内存管理的数据结构。
    恢复处理机上下文。
    注:总而言之就是很耗资源的
    (3)、进程的阻塞
    正在执行的进程,由于期待的某些事件未发生,如请求系统资源失败、等待某种操作的完成、
    新数据尚未到达或无新工作做等,则由系统自动执行阻塞原语(Block),使自己由运行状态
    变为阻塞状态。可见,进程的阻塞是进程自身的一种主动行为,也因此只有处于运行态的进
    程(获得 CPU),才可能将其转为阻塞状态。当进程进入阻塞状态,是不占用 CPU 资源的。
    (4)、文件描述符 fd
    文件描述符(File descriptor)是计算机科学中的一个术语,是一个用于表述指向文件的引用
    的抽象化概念。
    文件描述符在形式上是一个非负整数。实际上,它是一个索引值,指向内核为每一个进程所
    维护的该进程打开文件的记录表。当程序打开一个现有文件或者创建一个新文件时,内核向
    进程返回一个文件描述符。在程序设计中,一些涉及底层的程序编写往往会围绕着文件描述
    符展开。但是文件描述符这一概念往往只适用于 UNIX、Linux 这样的操作系统。
    (5)、缓存 I/O
    缓存 I/O 又被称作标准 I/O,大多数文件系统的默认 I/O 操作都是缓存 I/O。在 Linux 的
    缓存 I/O 机制中,操作系统会将 I/O 的数据缓存在文件系统的页缓存( page cache )
    中,也就是说,数据会先被拷贝到操作系统内核的缓冲区中,然后才会从操作系统内核的缓
    冲区拷贝到应用程序的地址空间。用户空间没法直接访问内核空间的,内核态到用户态的数
    据拷贝
    思考:为什么数据一定要先到内核区,直接到用户内存不是更直接吗?
    缓存 I/O 的缺点:
    数据在传输过程中需要在应用程序地址空间和内核进行多次数据拷贝操作,这些数据拷贝操
    作所带来的 CPU 以及内存开销是非常大的。
    同步(synchronous) IO 和异步(asynchronous) IO,阻塞(blocking) IO 和非阻塞
    (non-blocking)IO 分别是什么,到底有什么区别?这个问题其实不同的人给出的答案都可
    能不同,比如 wiki,就认为 asynchronous IO 和 non-blocking IO 是一个东西。这其实是因为
    不同的人的知识背景不同,并且在讨论这个问题的时候上下文(context)也不相同。所以,
    为了更好的回答这个问题,我先限定一下本文的上下文。
    本文讨论的背景是 Linux 环境下的 network IO。
    Stevens 在文章中一共比较了五种 IO Model:
     o
    o blocking IO
    o nonblocking IO
    o IO multiplexing
    o signal driven IO
    o asynchronous IO
    由于 signal driven IO 在实际中并不常用,所以我这只提及剩下的四种 IO Model。
    再说一下 IO 发生时涉及的对象和步骤。
    对于一个 network IO (这里我们以 read 举例),它会涉及到两个系统对象,一个是调
    用这个 IO 的 process (or thread),另一个就是系统内核(kernel)。当一个 read 操作发生时,
    它会经历两个阶段:
    1 等待数据准备 (Waiting for the data to be ready)
    2 将数据从内核拷贝到进程中 (Copying the data from the kernel to the process)
    记住这两点很重要,因为这些 IO Model 的区别就是在两个阶段上各有不同的情况。
    3、 blocking IO (阻塞 IO)
    在 linux 中,默认情况下所有的 socket 都是 blocking,一个典型的读操作流程大概
    是这样:
    当用户进程调用了 recvfrom 这个系统调用,kernel 就开始了 IO 的第一个阶段:
    准备数据。对于 network io 来说,很多时候数据在一开始还没有到达(比如,还
    没有收到一个完整的 UDP 包),这个时候 kernel 就要等待足够的数据到来。而在
    用户进程这边,整个进程会被阻塞。当 kernel 一直等到数据准备好了,它就会将
    数据从 kernel 中拷贝到用户内存,然后 kernel 返回结果,用户进程才解除 block
    的状态,重新运行起来。
    所以,blocking IO 的特点就是在 IO 执行的两个阶段都被 block 了。
    正常情况下的客户端和服务端进行交互就是阻塞 IO
    4、 non-blocking IO (非阻塞 IO)
    linux 下,可以通过设置 socket 使其变为 non-blocking。当对一个 non-blocking
    socket 执行读操作时,流程是这个样子:
    从图中可以看出,当用户进程发出 read 操作时,如果 kernel 中的数据还没有准
    备好,那么它并不会 block 用户进程,而是立刻返回一个 error。从用户进程角度
    讲 ,它发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。用
    户进程判断结果是一个 error 时,它就知道数据还没有准备好,于是它可以再次
    发送 read 操作。一旦 kernel 中的数据准备好了,并且又再次收到了用户进程的
    system call,那么它马上就将数据拷贝到了用户内存,然后返回。
    所以,用户进程其实是需要不断的主动询问 kernel 数据好了没有。
    注意:
    在网络 IO 时候,非阻塞 IO 也会进行 recvform 系统调用,检查数据是否准
    备好,与阻塞 IO 不一样,”非阻塞将大的整片时间的阻塞分成 N 多的小的阻塞, 所
    以进程不断地有机会 ‘被’ CPU 光顾”。即每次 recvform 系统调用之间,cpu 的权
    限还在进程手中,这段时间是可以做其他事情的,
    也就是说非阻塞的 recvform 系统调用调用之后,进程并没有被阻塞,内核
    马上返回给进程,如果数据还没准备好,此时会返回一个 error。进程在返回之
    后,可以干点别的事情,然后再发起 recvform 系统调用。重复上面的过程,循环
    往复的进行 recvform 系统调用。这个过程通常被称之为轮询。轮询检查内核数据,
    直到数据准备好,再拷贝数据到进程,进行数据处理。需要注意,拷贝数据整个
    过程,进程仍然是属于阻塞的状态。
    缺点:(1).不断地向系统发送调用。(2).不能及时的接受传递过来的数据。
    代码:
    控制台打印结果:
    5、 IO multiplexing(IO 多路复用)
    当用户进程调用了 select,那么整个进程会被 block,而同时,kernel 会“监视” 所有 select 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返
    回。这个时候用户进程再调用 read 操作,将数据从 kernel 拷贝到用户进程。
    这个图和 blocking IO 的图其实并没有太大的不同,事实上,还更差一些。因为
    这里需要使用两个 system call (select 和 recvfrom),而 blocking IO 只调用了
    一个 system call (recvfrom)。但是,用 select 的优势在于它可以同时处理多个
    connection。(多说一句。所以,如果处理的连接数不是很高的话,使用 select/epoll
    的 web server 不一定比使用 multi-threading + blocking IO 的 web server 性能更
    好,可能延迟还更大。select/epoll 的优势并不是对于单个连接能处理得更快,而
    是在于能处理更多的连接。)
    在 IO multiplexing Model 中,实际中,对于每一个 socket,一般都设置成为
    non-blocking,但是,如上图所示,整个用户的 process 其实是一直被 block 的。
    只不过 process 是被 select 这个函数 block,而不是被 socket IO 给 block。
    注意 1:select 函数返回结果中如果有文件可读了,那么进程就可以通过调用
    accept()或 recv()来让 kernel 将位于内核中准备到的数据 copy 到用户区。
    注意 2: select 的优势在于可以处理多个连接,不适用于单个连接
    (1).使用 select 方法
    select 最早于 1983 年出现在 4.2BSD 中,它通过一个 select()系统调用来监
    视多个文件描述符的数组,当 select()返回后,该数组中就绪的文件描述符便会
    被内核修改标志位,使得进程可以获得这些文件描述符从而进行后续的读写操
    作。 select 目前几乎在所有的平台上支持 , select 的一个缺点在于单个进
    程能够监视的文件描述符的数量存在最大限制,在 Linux 上一般为 1024,不过
    可以通过修改宏定义甚至重新编译内核的方式提升这一限制。 另外,select()
    所维护的存储大量文件描述符的数据结构,随着文件描述符数量的增大,其复制
    的开销也线性增长。同时,由于网络响应时间的延迟使得大量 TCP 连接处于非
    活跃状态,但调用 select()会对所有 socket 进行一次线性扫描,所以这也浪费
    了一定的开销。
    服务端:
    客户端:
    IO 多路复用:
    监听 conn 的变化,进行聊天,这就实现了 IO 多路复用,服务端可以监听多个
    客户端
    进行聊天,(如果使用普通方式,只能和一个聊完后关闭在和另外一个进行聊天)
    服务端:
    客户端:
    (2).使用 poll 方法和 epoll 方法
    Poll 方法和 epoll 方法 window 都不支持,linux 都支持,
    Poll 方法:
    它和 select 在本质上没有多大差别,但是 poll 没有最大数量的限制。 一般也
    不用它,相当于过渡阶段
    epoll 方法:
    直到 Linux2.6 才出现了由内核直接支持的实现方法,那就是 epoll。被公认为
    Linux2.6 下性能最好的多路 I/O 就绪通知方法。windows 不支持
    没有最大文件描述符数量的限制。 比如 100 个连接,有两个活跃了,epoll 会告
    诉用户这两个两个活跃了,直接取就 ok 了,而 select 是循环一遍。
    (了解)epoll 可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程
    哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么
    它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一
    些,但是代码实现相当复杂。
    另一个本质的改进在于 epoll 采用基于事件的就绪通知方式。在 select/poll 中,
    进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而
    epoll 事先通过 epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就
    绪时,内核会采用类似 callback 的回调机制,迅速激活这个文件描述符,当进程
    调用 epoll_wait()时便得到通知。
    所以市面上上见到的所谓的异步 IO,比如 nginx、Tornado、等,我们叫它异步 IO,
    实际上是 IO 多路复用。
    6、 IO 多路复用的触发方式
    在 linux 的 IO 多路复用中有水平触发,边缘触发两种模式,这两种模式的区别如下:
    水平触发:
    如果文件描述符已经就绪可以非阻塞的执行 IO 操作了,此时会触发通知.允许在
    任意时刻重复检测 IO 的状态,没有必要每次描述符就绪后尽可能多的执行
    IO.select,poll 就属于水平触发. 边缘触发:
    如果文件描述符自上次状态改变后有新的 IO 活动到来,此时会触发通知.在收到
    一个IO事件通知后要尽可能多的执行IO操作,因为如果在一次通知中没有执行完
    IO 那么就需要等到下一次新的 IO 活动到来才能获取到就绪的描述符.信号驱动式
    IO 就属于边缘触发. epoll 既可以采用水平触发,也可以采用边缘触发. 大家可能还不能完全了解这两种模式的区别,我们可以举例说明:一个管道收到了
    1kb 的数据,epoll 会立即返回,此时读了 512 字节数据,然后再次调用 epoll.这时如
    果是水平触发的,epoll 会立即返回,因为有数据准备好了.如果是边缘触发的不会
    立即返回,因为此时虽然有数据可读但是已经触发了一次通知,在这次通知到现在
    还没有新的数据到来,直到有新的数据到来 epoll 才会返回,此时老的数据和新的
    数据都可以读取到(当然是需要这次你尽可能的多读取). 下面我们还从电子的角度来解释一下:
    水平触发:也就是只有高电平(1)或低电平(0)时才触发通知,只要在这两种状态就
    能得到通知.上面提到的只要有数据可读(描述符就绪)那么水平触发的 epoll 就立
    即返回. 边缘触发:只有电平发生变化(高电平到低电平,或者低电平到高电平)的时候才触
    发通知.上面提到即使有数据可读,但是没有新的 IO 活动到来,epoll 也不会立即返
    回.
    7、 Asynchronous I/O(异步 IO)
    linux 下的 asynchronous IO 其实用得很少。先看一下它的流程:
    用户进程发起 read 操作之后,立刻就可以开始去做其它的事。而另一方面,从
    kernel 的角度,当它受到一个 asynchronous read 之后,首先它会立刻返回,所
    以不会对用户进程产生任何 block。然后,kernel 会等待数据准备完成,然后将数
    据拷贝到用户内存,当这一切都完成之后,kernel 会给用户进程发送一个 signal,
    告诉它 read 操作完成了。
    四、selectors 模块
    服务端:
    客户端:

你可能感兴趣的:(python多任务基础知识)