协程,又称微线程或纤程,是一个比进程和线程更加轻量级的解决方案,现代编程语言基本上都支持。那么协程究竟有什么特点,它是如何运作的呢?
进程和线程之间的关系我在这篇文章中做了一下比较分析。线程是程序的运行流,所有指令从头到尾按顺序一一执行,进程则是包含线程的容器。在单核CPU中,永远只有一条线程在运行,当然也只可能有一个进程运行,由操作系统内核负责进程和线程的切换。而协程是属于用户空间的,操作系统对其一无所知,由用户自己去做调度。显然用户自己调度比通过使用一个中断让操作系统调度会快很多,这就是协程快的秘诀了。
那么,协程究竟是一种什么样的存在呢?上面说到线程是一条指令的执行流,那么这个流的则由函数这些最小执行单元组成,函数则可能是包涵或者调用关系,无论是那种,一个函数只有只能完毕,下一个函数才能被执行,例如有两个函数A和B,A由于某些原因被阻塞,那么B肯定就不能运行。协程的出现就是为了解决这个问题,函数可以随时中断,去调用另外的函数。
Python和PHP5.5以上版本(包含)中,可以利用生成器中的yield在一定程度上实现协程。例如在一个车流量多的路口,摄像头获取车牌号,并保存到文本文件可以这样做:
#!/usr/bin/env python import random def getNum(action): action.next() while True: num=random.randint(1000, 9999) #获取车牌号,这里随机 stored = action.send(num) if stored > 500: break action.close() def storage(): r=0 while True: f=open("./car.txt","a") num = yield r print num if num: f.write(str(num)+" ") r=r+1 f.close() if __name__ == "__main__": S=storage() getNum(S)
<?php function getNum($action){ while(1){ $num=rand(1000,9999); $stored = $action->send($num); if($stored > 500){ break; } } } function storage(){ $r=0; while(1){ $num=(yield $r); if($num){ echo $num." "; file_put_contents("./car.txt",$num." ",FILE_APPEND); $r++; } } } $S=storage(); getNum($S);
yield这个关键字在很多语言中都有应用,在PHP和Python中,如果一个函数中含有yield,则这个函数会成为一个生成器(Generator)。上面的例子中,函数之间相互调用并没有在一个函数结束之后,另一个函数才开始运行,而是相互协作,所以称为”协程”,而非线程的抢占式多任务。需要注意的是Python的yield需要用next来启动,而PHP不需要。
yield只能在一定程度的实现协程,要更加深入理解和使用协程,可以尝试使用gevent,gevent为Python提供了比较完善的协程支持, 能帮助我们自动切换协程,可以获得极高的并发性能。