Bootstrap

线程和协程之间的区别

线程和协程之间的区别很大,甚至大过进程和线程之间的区别。线程建立在进程之上,协程建立在线程之上。那么协程是什么呢?

协程是一段计算机程序,它一般是一个协作类型的子程序,执行时允许暂停和恢复。协程非常适合实现熟知的程序,例如协作任务,异常,事件循环,迭代器,无限列表和管道。

协程与我们熟知的函数不同(函数也是一段计算机程序)。函数总是一个入口,一次返回,调用顺序明确,但是协程的调用可以中断,然后执行其它程序,在适当的时候再返回来继续执行,可以实现多次返回。

以异常为例。在操作系统中或者其它语言实现中,例如Java,Python等,异常函数被系统全局注册,当一个函数在执行过程中出现异常,系统捕获到异常后,会直接把执行流程跳转到异常函数处执行异常函数,然后也可以跳回异常点继续执行余下的程序。

Python使用yield实现协程。实现原理主要是利用了生成器函数对象是在堆中被创建的,如下图所示:

 

线程栈中的局部变量G1,G2,和G3分别引用了堆中的生成器对象。只要这种引用存在,Python虚拟机就不会回收到堆中的生成器。假设有这样一种执行路径:生成器G1放弃执行权,G2代码执行,然后G3,最后回到G1继续执行。此时G1可以在中断点继续执行余下程序,并且执行的上下文跟中断之前是一模一样的。这种执行程序的方式可以类别进程调度,但是跟进程调度又有着巨大的区别。首先,G1,G2和G3一直在同一个线程下执行,三者一通操作之后,该线程甚至可以一直没有被操作系统调度。

这种执行方式看起来好像平平无奇,但是在适合的场景下使用可以发挥出巨大的威力。

为应对Web高并发场景,各大平台主流的解决方案是使用多线程。对每个请求生成一个线程来处理,虽然线程创建占用资源少,但是不要忘记,如果使用到内核线程,那么内核会创建很多线程,这必然会占用宝贵的内核资源。另外,线程切换也会消耗一部分CPU资源,如果线程太多,那么可能部分CPU运算全部消耗在线程调度上。所以,Java网络编程新增了NIO模块,使用异步加线程池的方式实现高并发。

在Python中,使用协程正好可以解决这种IO密集型场景。例如,如果有1万个请求,那么可以生成1万个协程来处理。系统只需要在堆中生成1万个生成器对象即可,并且没有调度带来的消耗。

听起来很美,实际协程的使用是有限制的。协程中的IO操作必须是非阻塞的,如果调用阻塞接口,那么操作系统一旦感知,必然会发生线程调度,把当前线程换出CPU的runqueue队列。另外,Python单个线程的性能实在太感人,所以如果想充分发挥协程的威力,最好的方式是多进程加协程的组合。

怎么在Python中使用协程?可以自己实现,也可以使用第三方库,如eventlet等。

来个小例子:

import time
​
def consumer():
    while True:
        x = yield
        print 'consume value:%s ' % x
        time.sleep(1)
​
def producer(consumer):
    c = next(consumer)
    for i in range(3):
        consumer.send(i)
        print 'produce value:%s ' % i
​
producer(consumer())

输出:

consume value:0 
produce value:0 
consume value:1 
produce value:1 
consume value:2 
produce value:2 

生产者和消费者交替执行。

小结

协程是在线程的基础上进行分时复用并发执行不同的程序段。它不是函数,也不是用户线程,更不是进程。

;