异步 IO(一)

378 阅读5分钟

异步 IO

  1. CPU 的速度远快于 IO 的速度,如网络和磁盘等。在一个线程中,CPU执行代码的速度极快,然而碰到 IO 操作,就需要等待 IO 操作完成才能进行下一步操作。这种情况称为同步 IO。在 IO 操作过程中,当前线程被挂起,而其他需要 CPU 执行的代码就无法在当前线程执行了。

  2. 因为 IO 操作会阻塞当前线程,所以提出了多进程或者多线程来并发执行代码,每个用户分配一个线程,不影响其他用户。虽然多进程或者多线程解决了并发问题,但是数量过多,会导致性能下降。我们需要解决的问题是 CPU 高速执行能力和 IO 设备的龟速不匹配问题,这种方式只是一种方法。

  3. 另一种方法就是异步 IO,异步 IO 需要一个消息循环,在消息循环中,主线程不断地重复“读取消息-处理消息”这一过程。当遇到 IO 操作时,代码是负责发送 IO 请求,不等待 IO 结果,然后直接结束本轮消息处理,进入下一轮消息处理过程。当 IO 操作完成后,将收到一条“IO 完成”的消息,处理该消息时就可以直接获取 IO 操作结果。

    在“发出 IO 请求”到收到“IO 完成”的这段时间里,同步 IO 模型下,主线程只能挂起,但异步 IO 模型下,主线程并没有休息,而是在消息循环中继续处理其他消息。在异步 IO 模型下,一个线程就可以同时处理多个 IO 请求,并且没有切换线程的操作。对于大多数IO 密集型的应用程序,使用异步 IO 将大大提升系统的多任务处理能力。

协程

  1. 协程又叫微线程,英文名Coroutine,像子程序(又叫函数),但是在执行过程中可以中断,然后去执行别的子程序,这种中断不是函数调用,而是类似于 CPU 中断,如:

     def A():
     	print('1')
     	print('2')
     	print('3')
    
     def B():
     	print('x')
     	print('y')
     	print('z')
    

如果是协程来执行,可能在执行 A 的过程中去执行 B,但是 A 中是没有调用 B 的,结果可能是:

	1
	2
	x
	y
	3
	z
  1. 协程有点像多线程,但是比多线程有优势。

    • 第一个优势就是执行效率很高,因为子程序切换不是线程切换,而是程序自身控制,所以没有线程切换额开销,数量越多,协程的性能优势越能体现。
    • 第二个优势就是不需要多线程的锁机制,因为只是一个线程,不存在读写变量冲突,只需要判断状态就好了,所以执行效率比多线程高很多。
  2. 协程是一个线程执行,为了利用多核 CPU,最简单的方法就是多进程+协程,即充分利用多核,又充分发挥协程的高效率。

  3. Python 对协程的支持是通过 generator 实现的。在 generator 中,我们不但可以通过 for 循环来迭代,还可以不断调用 next() 函数获取由 yield 语句返回的下一个值。但是 Python 的 yield 不但可以返回一个值,它还可以接收调用者发出的参数。

  4. 传统的生产者-消费者模型是一个线程写消息,一个线程取消息,通过锁机制控制队列和等待,但一不小心就可能死锁。如果改用协程,生产者生产消息后,直接通过 yield 跳转到消费者开始执行,待消费者执行完毕后,切换回生产者继续生产,效率极高:

     def consumer():
         r = ''
         count = 0
         print("启动 generator ")
         while True:
             print("消费第 %s 次" %count)
             n = yield r
             if not n:
                 return
             print('[CONSUMER] Consuming %s...' % n)
             r = '200 OK'
             count += 1
     def produce(c):
         c.send(None)
         n = 0
         while n < 5:
             n = n + 1
             print('[PRODUCER] Producing %s...' % n)
             r = c.send(n)
             print('[PRODUCER] Consumer return: %s' % r)
             print("一轮的数据生成-消费结束")
             print("===================")
         c.close()
     
     c = consumer()
     produce(c)
    

    运行结果:

     启动 generator 
     消费第 0 次
     [PRODUCER] Producing 1...
     [CONSUMER] Consuming 1...
     消费第 1 次
     [PRODUCER] Consumer return: 200 OK
     一轮的数据生成-消费结束
     ===================
     [PRODUCER] Producing 2...
     [CONSUMER] Consuming 2...
     消费第 2 次
     [PRODUCER] Consumer return: 200 OK
     一轮的数据生成-消费结束
     ===================
     [PRODUCER] Producing 3...
     [CONSUMER] Consuming 3...
     消费第 3 次
     [PRODUCER] Consumer return: 200 OK
     一轮的数据生成-消费结束
     ===================
     [PRODUCER] Producing 4...
     [CONSUMER] Consuming 4...
     消费第 4 次
     [PRODUCER] Consumer return: 200 OK
     一轮的数据生成-消费结束
     ===================
     [PRODUCER] Producing 5...
     [CONSUMER] Consuming 5...
     消费第 5 次
     [PRODUCER] Consumer return: 200 OK
     一轮的数据生成-消费结束
     ===================
    

    注意到 consumer 函数是一个 generator ,把一个 consumer 传入 produce 后:

    1. 首先调用 c.send(None) 启动生成器;

    2. 然后,一旦生产了东西,通过 c.send(n) 切换到 consumer 执行;

    3. consumer 通过 yield 拿到消息,处理,在下一次 while 循环时通过 yield 把结果 r 传回;

    4. produce 拿到 consumer 处理的结果,继续生产下一条消息;

    5. produce 决定不生产了,通过 c.close() 关闭 consumer,整个过程结束。

    6. 整个流程无锁,由一个线程执行,produce 和 consumer 协作完成任务,所以称为“协程”,而非线程的抢占式多任务。

本文参考:

廖雪峰: www.liaoxuefeng.com/wiki/101695…

感谢支持 支付宝

支付宝

微信

微信