python 协程 深入浅出

说到并发编程,大家容易想到的就是:进程、线程、协程、异步IO。四者在实现上却有共通之处,不外乎调度二字。

进程:操作系统进程系统调度,调度号:pid,基本由操作系统提供调度支持

线程:操作系统线程调度,调度号:TCB,虚拟机提供一部分支持

协程:程序自己进行调度,调度号:函数名,全部由程序自身完成。

异步IO:由消息中间件负责调度,调度号:消息队列。

进程、线程、协程它们三个实现的是时间复用,达到逻辑上的同步;而异步IO则仅仅实现了应用的解耦合,必须要配合前面三者中的一个或多个来实现并发的处理;

下面专门讲协程

协程,顾名思义就是协同合作的程序,一个程序里可以有很多协程,这些协程在主线程的调度下,表现得像是并发执行得一样;这样得好处就是:轻量级。

人们常说:线程是轻量级的进程;到了协程这里,我们可以说协程是最轻量级的执行单元;不会再有比协程更轻的了;一个协程可以是一个函数或者任何可调用对象,它的调度也完全由程序自身控制,因此可以将其调度开销按照自身需要降到最低。这就是它的优势所在;

本文刚开始就说到了,处理并发的核心思想就是:调度;所以不要觉得协程是多么新的东西;早在你用C语言编写俄罗斯方块程序的时候,就用过了;俄罗斯方块需要一个专门统计和显示分数的函数,还需要一个控制方块运动翻转的函数,这两个函数需要是并发执行的;你当时的做法肯定不是用多线程,而是像下面一样:

while(1){
sleep(0.05)
counter();
move();
}

现在你知道协程是什么东西了吧,就是上面的counter() move()

下面由浅入深的讲python协程的3个境界:“自己实现的协程”、“”、“利用第三方库实现的协程”

说到并发,最简单的模型就是生产者-消费者模型:生产者每隔一段时间生产一个产品,而消费者也每隔一定时间消费一个产品,生产者一般认为只有一个,消费者可以有多个;

自己实现的协程

直接上代码,有点类似俄罗斯方块的实现

from time import sleep,time
from Queue import Queue
queue=Queue(10)
class producer():
	def __init__(self, interval):
		self.interval = interval
		self.count = 1
		self.state = "free"
	def run(self):
		if self.state == "free":
			if not queue.full():
				self.start_time = time()
				print "producer is making cake[%d]"%(self.count)
				self.state = "busy"
			else:
				print "more than 10 cakes exits!,producer is waiting"
		if self.state == "busy":
			if time()-self.start_time > self.interval:
				queue.put("cake[%s]"%self.count)
				self.count+=1
				self.state = "free"
class consumer():
	def __init__(self,name, interval, producer):
		self.interval=interval
		self.producer=producer
		self.state="free"
		self.cake=None
		self.name=name
	def run(self):
		if self.state=="free":
			if not queue.empty():
				self.cake=queue.get()
				self.start_time = time()
				self.state="busy"
			else:
				print "no cake exits, %s is waiting!"%self.name
		if self.state=="busy":
			if time() - self.start_time > self.interval:
				print "%s finished eating %s"%(self.name, self.cake)
				self.state="free"
			
def main():
	p = producer(1)
	c1 = consumer("mom",2, p)
	c2 = consumer("child",3, p)
	while(True):
		sleep(0.1)
		p.run()
		c1.run()
		c2.run()
		
main()
	

利用语言特性yield实现的协程

还是那句话,要实现并发就是实现调度;而调度的本质是什么?是交警叔叔一个手势让一辆车停,让令一辆车跑;我们这里的车是函数;python里面如果没有使用yield的函数,我们是无法让他执行到一半停止的,所以我们才会像上面那样在while循环里每次调用一次这个函数,而又通过类对象存储状态,使得看上去好像有一个东西跑跑听听一样;有了yield这个语言特性,我们就可以将状态直接当作函数的状态了;这使得我们要做的工作少了很多,比如上面的sleep(0.1)

代码如下:

from Queue import Queue
from time import time
queue=Queue(10)

def producer(name, interval):
	num=0
	state="free"
	while True:
		if state=="free":
			if not queue.full():
				state="busy"
				start_time=time()
				print "%s is making cake[%d]"%(name, num)
				num+=1
			else:
				print "more than 10 cakes exists, %s is waiting"%name
			yield
			continue
		if state=="busy":
			if time()-start_time > interval:
				queue.put("cake[%d]"%num)
				state="free"
			yield
			continue

def consumer(name, interval):
	state = "free"
	while True:
		if state == "free":
			if not queue.empty():
				cake = queue.get()
				start_time = time()
				state = "busy"	
			else:
				print "no cake exists, %s is waiting"%name
			yield
			continue
		if state == "busy":
			if time()-start_time > interval:
				print "%s finished eating %s"%(name,cake)
				state = "free"
			yield
			continue

sister=consumer("sister",2)
brother=consumer("brother",3)
mom = producer("mom",1)
while True:
	mom.next()
	brother.next()
	sister.next()
	

我们再做下改进,让它看上去更thread-like,也就是不要代码里出现上面这样的while True,而用start()

from Queue import Queue
from time import time
class Routine(object):
	def __init__(self, name, target=None, parent=None):
		self.name=name
		self.parent=parent
		self.children=[]
		self.target=target
	def add(self, child):
		self.children.append(child)
	def next(self):
		if self.target:
			return self.target.next()
	def start(self):
		if self.parent:
			self.parent.add(self)
		else:
			while True:
				for i in self.children:
					i.next()

def producer(name, interval):
	num=0
	state="free"
	while True:
		if state=="free":
			if not queue.full():
				state="busy"
				start_time=time()
				print "%s is making cake[%d]"%(name, num)
				num+=1
			else:
				print "more than 10 cakes exists, %s is waiting"%name
			yield
			continue
		if state=="busy":
			if time()-start_time > interval:
				queue.put("cake[%d]"%num)
				state="free"
			yield
			continue

def consumer(name, interval):
	state = "free"
	while True:
		if state == "free":
			if not queue.empty():
				cake = queue.get()
				start_time = time()
				state = "busy"	
			else:
				print "no cake exists, %s is waiting"%name
			yield
			continue
		if state == "busy":
			if time()-start_time > interval:
				print "%s finished eating %s"%(name,cake)
				state = "free"
			yield
			continue

queue=Queue(10)
sister=consumer("sister",2)
brother=consumer("brother",3)
mom = producer("mom",1)
	
main_routine=Routine(__name__)
routineA=Routine("A",sister,main_routine)
routineB=Routine("B",brother,main_routine)
routineC=Routine("C",mom,main_routine)
routineA.start()
routineB.start()
routineC.start()
main_routine.start()
	

上面的改进我们已经对协程的实现进行了一定的封装,但是还是有些不足;比如它需要自己去定义生成器;当然如果以“自己实现的协程”里的那种方式进行封装是不需要生成器的;那么有没有别人已经写好的封装呢?或者有没有更好用的协程库呢?因为我们需要让专业的人来做专业的事,自己造车多数情况下都是吃力不讨好的,需要大量的测试和优化,没有一个社区的力量是行不通的;

利用第三方库实现的协程

下面代码转自:

https://www.liaoxuefeng.com/wiki/001374738125095c955c1e6d8bb493182103fac9270762a000/001407503089986d175822da68d4d6685fbe849a0e0ca35000

from gevent import monkey; monkey.patch_all()
import gevent
import urllib2

def f(url):
    print('GET: %s' % url)
    resp = urllib2.urlopen(url)
    data = resp.read()
    print('%d bytes received from %s.' % (len(data), url))

gevent.joinall([
        gevent.spawn(f, 'https://www.python.org/'),
        gevent.spawn(f, 'https://www.yahoo.com/'),
        gevent.spawn(f, 'https://github.com/'),
])

它的输出如下:

GET: https://www.python.org/
GET: https://www.yahoo.com/
GET: https://github.com/
45661 bytes received from https://www.python.org/.
14823 bytes received from https://github.com/.
304034 bytes received from https://www.yahoo.com/.

可以看到,当遇到阻塞(如IO操作,或者gevent.sleep(0)),gevent的调度器会自动切换协程去执行;而且它的协程都是一个个的函数,这样就很nice了:看上去像线程,其实是协程。

猜你喜欢

转载自my.oschina.net/backbye/blog/1809464