• python并发编程--协程---从菜鸟到老鸟(四)


    如何更好地并发编程

    简介

    python不仅内置了multiprocess模块,而且还内置了asyncio和concurrent模块。除了要分析这两个内置的,其实我们还要再下面另一个第三方joblib包。

    我们经常喜欢单机处理数据,或者数据量一大就采用分布式的方式,其实并发编程是一个很好的选择。

    • asyncio
    • concurrent
    • joblib

    但是第一件事我们要知道采用进程的最终目的是什么?一般都是达到异步IO。那么异步IO是什么?

    异步IO

    异步IO是个好东西,在网络读写场景中可以大大提高程序的并发能力,比如爬虫、web服务等。这样的好东西自然也要在Python中可以使用。不过,在漫长的Python2时代,官方并没有推出一个自己的异步IO库,到了Python 3.4 才推出。

    我们先从各种IO模型中去理解异步IO,那么IO可以分为几类呢?同步IO、异步IO、阻塞IO、非阻塞IO

    • 同步是指代码调IO操作时,必须等待IO操作完成才返回的调用方式。
    • 异步是指代码调用IO操作时,不必等IO操作完成就返回的调用方式。
    • 阻塞是指调用函数时候当前线程被挂起。
    • 阻塞是指调用函数时候当前线程不会被挂起,而是立即返回。

    IO模型

    • 阻塞IO模型

    使用recv的默认参数一直等数据直到拷贝到用户空间,这段时间内进程始终阻塞。A同学用杯子装水,打开水龙头装满水然后离开。这一过程就可以看成是使用了阻塞IO模型,因为如果水龙头没有水,他也要等到有水并装满杯子才能离开去做别的事情。很显然,这种IO模型是同步的。

    image

    • 非阻塞IO模型

    改变flags,让recv不管有没有获取到数据都返回,如果没有数据那么一段时间后再调用recv看看,如此循环。B同学也用杯子装水,打开水龙头后发现没有水,它离开了,过一会他又拿着杯子来看看……在中间离开的这些时间里,B同学离开了装水现场(回到用户进程空间),可以做他自己的事情。这就是非阻塞IO模型。但是它只有是检查无数据的时候是非阻塞的,在数据到达的时候依然要等待复制数据到用户空间(等着水将水杯装满),因此它还是同步IO。

    image

    • IO复用模型

    这里在调用recv前先调用select或者poll,这2个系统调用都可以在内核准备好数据(网络数据到达内核)时告知用户进程,这个时候再调用recv一定是有数据的。因此这一过程中它是阻塞于select或poll,而没有阻塞于recv,有人将非阻塞IO定义成在读写操作时没有阻塞于系统调用的IO操作(不包括数据从内核复制到用户空间时的阻塞,因为这相对于网络IO来说确实很短暂),如果按这样理解,这种IO模型也能称之为非阻塞IO模型,但是按POSIX来看,它也是同步IO,那么也和楼上一样称之为同步非阻塞IO吧。

    这种IO模型比较特别,分个段。因为它能同时监听多个文件描述符(fd)。这个时候C同学来装水,发现有一排水龙头,舍管阿姨告诉他这些水龙头都还没有水,等有水了告诉他。于是等啊等(select调用中),过了一会阿姨告诉他有水了,但不知道是哪个水龙头有水,自己看吧。于是C同学一个个打开,往杯子里装水(recv)。这里再顺便说说鼎鼎大名的epoll(高性能的代名词啊),epoll也属于IO复用模型,主要区别在于舍管阿姨会告诉C同学哪几个水龙头有水了,不需要一个个打开看(当然还有其它区别)。

    image

    • 信号驱动IO模型

    通过调用sigaction注册信号函数,等内核数据准备好的时候系统中断当前程序,执行信号函数(在这里面调用recv)。D同学让舍管阿姨等有水的时候通知他(注册信号函数),没多久D同学得知有水了,跑去装水。是不是很像异步IO?

    很遗憾,它还是同步IO(省不了装水的时间啊)。

    image

    • 异步IO模型

    调用aio_read,让内核等数据准备好,并且复制到用户进程空间后执行事先指定好的函数。E同学让舍管阿姨将杯子装满水后通知他。整个过程E同学都可以做别的事情(没有recv),这才是真正的异步IO。

    image

    • 总结

    一般来讲:阻塞IO模型、非阻塞IO模型、IO复用模型(select/poll/epoll)、信号驱动IO模型都属于同步IO,因为阶段2是阻塞的(尽管时间很短)。只有异步IO模型是符合POSIX异步IO操作含义的,不管在阶段1还是阶段2都可以干别的事。

    • IO的拓展

    其实整个IO过程再加上生成者就可以组建成生产消费模型。

    那我们在看看进程与线程在操作系统中所处的地位:

     那么为什么我们还要引入另一种更细小的程序(操作程序)的单位?

    我们先看在定义生产与消费模型中,我们必须要做些什么

    1. 定义了生产者与消费者。
    2. 生产者生产数据,向同步队列当中插入数据。
    3. 消费者循环监听同步队列,当队列有数据时拉取数据。
    4. 如果队列满了(达到n个元素),生产者阻塞。
    5. 如果队列空了,消费者阻塞。

    上面的方法正确地实现了生产者/消费者模式,但是却并不是一个高性能的实现。

    为什么性能不高呢?因为我们不可避免涉及了如下操作:

    1. 涉及到同步锁。
    2. 涉及到线程阻塞状态和可运行状态之间的切换。
    3. 涉及到线程上下文的切换。

    同时因为GIL锁的存在,python是无法做到几个线程在一个CPU单核中“异步“并发。

    所以往往python高并编程中,协程是不可缺少的存在,那我们就不得不学习一下协程。

    了解协程

    协程,英文Coroutines、又称微线程,纤程,是一种比线程更加轻量级的存在。

    正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程,所以可以实现单线程下的并发,

     

     特点如下:

    1. python的线程属于内核级别的,即由操作系统控制调度(如单线程遇到io或执行时间过长就会被迫交出cpu执行权限,切换其他线程运行)

    2. 单线程内开启协程,一旦遇到io,就会从应用程序级别(而非操作系统)控制切换,以此来提升效率(!!!非io操作的切换与效率无关)

    python中异步IO发展史

    Python 2 时代

    官方并没有异步IO的支持,但是有几个第三方库通过事件或事件循环(Event Loop)实现了异步IO,它们是:

      • twisted: 是事件驱动的网络库
      • gevent: greenlet + libevent(后来是libev或libuv)。通过协程(greenlet)和事件循环库(libev,libuv)实现的gevent使用很广泛。
      • tornado: 支持异步IO的web框架。自己实现了IOLOOP。

    Python 3时代

    异步io的好处在于避免的线程的开销和切换,而且我们都知道python其实是没有多线程的,只是通过底层线层锁实现的多线程。另一个好处在于避免io操作(包含网络传输)的堵塞时间。

    Python 3.4 加入了asyncio 库,使得Python有了支持异步IO的官方库。这个库,底层是事件循环(EventLoop),上层是协程和任务。

    asyncio可以实现单线程并发IO操作。如果仅用在客户端,发挥的威力不大。如果把asyncio用在服务器端,例如Web服务器,由于HTTP连接就是IO操作,因此可以用单线程+coroutine实现多用户的高并发支持。

    asyncio实现了TCP、UDP、SSL等协议,aiohttp则是基于asyncio实现的HTTP框架。

    如何使用asyncio

  • 相关阅读:
    Error: could not open `C:Program FilesJavajre6libi386jvm.cfg'
    异常:java.lang.IllegalStateException: Ambiguous handler methods mapped for HTTP path '/app/userInfoMaint/getProvince.do'
    解析Java反射
    Cause: java.sql.SQLException: 无效的列索引
    Internet Explorer 无法打开该 Internet 站点,请求的站点不可用或无法找到
    解决The JSP specification requires that an attribute name is preceded by whitespace问题
    pl/sql的to_char和to_date
    oracle 在xml中批量插入,批量修改及多组条件查询
    时间转换模板
    Java网络编程从入门到精通(5):使用InetAddress类的getHostName方法获得域名
  • 原文地址:https://www.cnblogs.com/wqbin/p/12593373.html
Copyright © 2020-2023  润新知