Stackless Python并发式编程介绍
转载自 http://blog.csdn.net/changbaohua/article/details/3777410
截取部分自己需要看的内容
1 介绍
1.1 为什么要使用Stackless
- 改进程序结构
- 增进代码可读性
- 提高编程人员生产力
以上是Stackless Python很简明的释义,但其对我们意义何在?——就在于Stackless提供的并发建模工具,比目前其它大多数传统编程语言所提供的,都更加易用: 不仅是Python自身,也包括Java、C++,以及其它。尽管还有其他一些语言提供并发特性,可它们要么是主要用于学术研究的(如 Mozart/Oz),要么是罕为使用、或用于特殊目的的专业语言(如Erlang)。而使用stackless,你将会在Python本身的所有优势之 上,在一个(但愿)你已经很熟悉的环境中,再获得并发的特性。
这自然引出了个问题:为什么要并发?
1.1.1 现实世界就是并发的
def familyTacoNight():husband.eat(dinner)wife.eat(dinner)son.eat(dinner)daughter.eat(dinner)
1.1.2 并发可能是(仅仅可能是)下一个重要的编程范式
1.2 安装stackless
2 stackless起步
本章简要介绍了 stackless 的基本概念,后面章节将基于这些基础,来展示更加实用的功能。
2.1 微进程(tasklet)
微进程是stackless的基本构成单元,你可以通过提供任一个Python可调用对象(通常为函数或类的方法)来建立它,这将建立一个微进程并将其添加到调度器。这是一个快速演示:
Python 2.4.3 Stackless 3.1b3 060504 (#69, May 3 2006, 19:20:41) [MSC v.1310 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import stackless >>> >>> def print_x(x): ... print x ... >>> stackless.tasklet(print_x)('one') <stackless.tasklet object at 0x00A45870> >>> stackless.tasklet(print_x)('two') <stackless.tasklet object at 0x00A45A30> >>> stackless.tasklet(print_x)('three') <stackless.tasklet object at 0x00A45AB0> >>> >>> stackless.run() one two three >>>
注意,微进程将排起队来,并不运行,直到调用 stackless.run() 。
2.2 调度器(scheduler)
调度器控制各个微进程运行的顺序。如果刚刚建立了一组微进程,它们将按照建立的顺序来执行。在现实中,一般会建立一组可以再次被调度的微进程,好让每个都有轮次机会。一个快速演示:
Python 2.4.3 Stackless 3.1b3 060504 (#69, May 3 2006, 19:20:41) [MSC v.1310 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import stackless >>> >>> def print_three_times(x): ... print "1:", x ... stackless.schedule() ... print "2:", x ... stackless.schedule() ... print "3:", x ... stackless.schedule() ... >>> >>> stackless.tasklet(print_three_times)('first') <stackless.tasklet object at 0x00A45870> >>> stackless.tasklet(print_three_times)('second') <stackless.tasklet object at 0x00A45A30> >>> stackless.tasklet(print_three_times)('third') <stackless.tasklet object at 0x00A45AB0> >>> >>> stackless.run() 1: first 1: second 1: third 2: first 2: second 2: third 3: first 3: second 3: third >>>
2.3 通道(channel)
- 能够在微进程之间交换信息。
- 能够控制运行的流程。
又一个快速演示:
C:>c:python24python Python 2.4.3 Stackless 3.1b3 060504 (#69, May 3 2006, 19:20:41) [MSC v.1310 32 bit (Intel)] on win32 Type "help", "copyright", "credits" or "license" for more information. >>> import stackless >>> >>> channel = stackless.channel() >>> >>> def receiving_tasklet(): ... print "Recieving tasklet started" ... print channel.receive() ... print "Receiving tasklet finished" ... >>> def sending_tasklet(): ... print "Sending tasklet started" ... channel.send("send from sending_tasklet") ... print "sending tasklet finished" ... >>> def another_tasklet(): ... print "Just another tasklet in the scheduler" ... >>> stackless.tasklet(receiving_tasklet)() <stackless.tasklet object at 0x00A45B30> >>> stackless.tasklet(sending_tasklet)() <stackless.tasklet object at 0x00A45B70> >>> stackless.tasklet(another_tasklet)() <stackless.tasklet object at 0x00A45BF0> >>> >>> stackless.run() Recieving tasklet started Sending tasklet started send from sending_tasklet Receiving tasklet finished Just another tasklet in the scheduler sending tasklet finished >>>
接收的微进程调用 channel.receive() 的时候,便阻塞住,这意味着该微进程暂停执行,直到有信息从这个通道送过来。除了往这个通道发送信息以外,没有其他任何方式可以让这个微进程恢复运行。
若有其他微进程向这个通道发送了信息,则不管当前的调度到了哪里,这个接收的微进程都立即恢复执行;而发送信息的微进程则被转移到调度列表的末尾,就像调用了 stackless.schedule() 一样。
同样注意,发送信息的时候,若当时没有微进程正在这个通道上接收,也会使当前微进程阻塞:
>>> stackless.tasklet(sending_tasklet)() <stackless.tasklet object at 0x00A45B70> >>> stackless.tasklet(another_tasklet)() <stackless.tasklet object at 0x00A45BF0> >>> >>> stackless.run() Sending tasklet started Just another tasklet in the scheduler >>> >>> stackless.tasklet(another_tasklet)() <stackless.tasklet object at 0x00A45B30> >>> stackless.run() Just another tasklet in the scheduler >>> >>> #Finally adding the receiving tasklet ... >>> stackless.tasklet(receiving_tasklet)() <stackless.tasklet object at 0x00A45BF0> >>> >>> stackless.run() Recieving tasklet started send from sending_tasklet Receiving tasklet finished sending tasklet finished
发送信息的微进程,只有在成功地将数据发送到了另一个微进程之后,才会重新被插入到调度器中。
2.4 总结
以上涵盖了stackless的大部分功能。似乎不多是吧?——我们只使用了少许对象,和大约四五个函数调用,来进行操作。但是,使用这种简单的API作为基本建造单元,我们可以开始做一些真正有趣的事情。
3 协程(coroutine)
3.1 子例程的问题
大多数传统编程语言具有子例程的概念。一个子例程被另一个例程(可能还是其它某个例程的子例程)所调用,或返回一个结果,或不返回结果。从定义上说,一个子例程是从属于其调用者的。
def ping():print "PING"pong()def pong():print "PONG"ping()ping()
有经验的编程者会看到这个程序的问题所在:它导致了堆栈溢出。如果运行这个程序,它将显示一大堆讨厌的跟踪信息,来指出堆栈空间已经耗尽。
3.1.1 堆栈
帧 | 堆栈 |
1 | ping 被调用 |
2 | ping 被调用,所以 pong 被调用 |
3 | ping 被调用,所以 pong 被调用,所以 ping 被调用 |
4 | ping 被调用,所以 pong 被调用,所以 ping 被调用,所以 pong 被调用 |
5 | ping 被调用,所以 pong 被调用,所以 ping 被调用,所以 pong 被调用,所以 ping 被调用 |
6 | ping 被调用,所以 pong 被调用,所以 ping 被调用,所以 pong 被调用,所以 ping 被调用…… |
现在假设,这个页面的宽度就表示系统为堆栈所分配的全部内存空间,当其顶到页面的边缘的时候,将会发生溢出,系统内存耗尽,即术语“堆栈溢出”。
3.1.2 那么,为什么要使用堆栈?
3.2 走进协程
此时,将堆栈弄溢出是有点愚蠢的。 ping() 和 pong() 本不是真正意义的子例程,因为其中哪个也不从属于另一个,它们是“协程”,处于同等的地位,并可以彼此间进行无缝通信。
帧 | 堆栈 |
1 | ping 被调用 |
2 | pong 被调用 |
3 | ping 被调用 |
4 | pong 被调用 |
5 | ping 被调用 |
6 | pong 被调用 |
# # pingpong_stackless.py #import stacklessping_channel = stackless.channel() pong_channel = stackless.channel()def ping():while ping_channel.receive(): #在此阻塞print "PING"pong_channel.send("from ping")def pong():while pong_channel.receive():print "PONG"ping_channel.send("from pong")stackless.tasklet(ping)() stackless.tasklet(pong)()# 我们需要发送一个消息来初始化这个游戏的状态 # 否则,两个微进程都会阻塞 stackless.tasklet(ping_channel.send)('startup')stackless.run()
3.3 总结
4 轻量级线程
与当今的操作系统中内建的、和标准Python代码中所支持的普通线程相比,“微线程”要更为轻量级,正如其名称所暗示。它比传统线程占用更少的内存,并且微线程之间的切换,要比传统线程之间的切换更加节省资源。
为了准确说明微线程的效率究竟比传统线程高多少,我们用两者来写同一个程序。
4.1 hackysack模拟
Hackysack是一种游戏,就是一伙脏乎乎的小子围成一个圈,来回踢一个装满了豆粒的沙包,目标是不让这个沙包落地,当传球给别人的时候,可以耍各种把戏。踢沙包只可以用脚。
在我们的简易模拟中,我们假设一旦游戏开始,圈里人数就是恒定的,并且每个人都是如此厉害,以至于如果允许的话,这个游戏可以永远停不下来。
4.2 游戏的传统线程版本
import thread import random import sys import Queueclass hackysacker:counter = 0def __init__(self,name,circle):self.name = nameself.circle = circlecircle.append(self)self.messageQueue = Queue.Queue()thread.start_new_thread(self.messageLoop,())def incrementCounter(self):hackysacker.counter += 1if hackysacker.counter >= turns:while self.circle:hs = self.circle.pop()if hs is not self:hs.messageQueue.put('exit')sys.exit()def messageLoop(self):while 1:message = self.messageQueue.get()if message == "exit":debugPrint("%s is going home" % self.name)sys.exit()debugPrint("%s got hackeysack from %s" % (self.name, message.name))kickTo = self.circle[random.randint(0,len(self.circle)-1)]debugPrint("%s kicking hackeysack to %s" % (self.name, kickTo.name))self.incrementCounter()kickTo.messageQueue.put(self)def debugPrint(x):if debug:print xdebug=1 hackysackers=5 turns = 5def runit(hs=10,ts=10,dbg=1):global hackysackers,turns,debughackysackers = hsturns = tsdebug = dbghackysacker.counter= 0circle = []one = hackysacker('1',circle)for i in range(hackysackers):hackysacker(`i`,circle)one.messageQueue.put(one)try:while circle:passexcept:#有时我们在清理过程中会遇到诡异的错误。passif __name__ == "__main__":runit(dbg=1)
一个“玩者”类的初始化用到了其名字,和一个指向包含了所有玩者的全局列表 circle 的引用,还有一个继承自Python标准库中的Queue类的消息队列。
如果收到了另一个消息——指定其收到了沙包,玩者则从圈中随机选取一个其他玩者,通过向其发送一条消息来指定,将沙包再踢给它。
由类成员变量 hackysacker.counter 进行计数,当沙包被踢够了指定的次数时,将会向圈中的所有玩者都发送一条特殊的 ‘exit’ 消息。
注意,当全局变量debug为非零的时候,还有个函数debugPrint可以输出信息。我们可以使这游戏输出到标准输出,但当计时的时候,这会影响精确度。
C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:python24python.exe hackysackthreaded.py1 got hackeysack from 1 1 kicking hackeysack to 4 4 got hackeysack from 1 4 kicking hackeysack to 0 0 got hackeysack from 4 0 kicking hackeysack to 1 1 got hackeysack from 0 1 kicking hackeysack to 3 3 got hackeysack from 1 3 kicking hackeysack to 3 4 is going home 2 is going home 1 is going home 0 is going home 1 is going homeC:Documents and SettingsgrantDesktopwhy_stacklesscode>
如我们所见,所有玩者到了一起,并很快地进行了一场游戏。现在,我们对若干次实验运行过程进行计时。Python标准库中有一个 timeit.py 程序,可以用作此目的。那么,我们也同时关掉调试输出:
C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:python24python.exe c:Python24libtimeit.py -s "import hackysackthreaded" hackysackthreaded.runit(10,1000,0) 10 loops, best of 3: 183 msec per loop
在我的机器上,十个玩者共进行1000次传球,共使用了183毫秒。我们来增加玩者的数量:
C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:python24python.exe c:Python24libtimeit.py -s "import hackeysackthreaded" hackeysackthreaded.runit(100,1000,0) 10 loops, best of 3: 231 msec per loopC:Documents and SettingsgrantDesktopwhy_stacklesscode>c:python24python.exe c:Python24libtimeit.py -s "import hackysackthreaded" hackysackthreaded.runit(1000,1000,0) 10 loops, best of 3: 681 msec per loopC:Documents and SettingsgrantDesktopwhy_stacklesscode>c:python24python.exe c:Python24libtimeit.py -s "import hackysackthreaded" hackysackthreaded.runit(10000,1000,0) Traceback (most recent call last):File "c:Python24libtimeit.py", line 255, in mainx = t.timeit(number)File "c:Python24libtimeit.py", line 161, in timeittiming = self.inner(it, self.timer)File "<timeit-src>", line 6, in innerFile ".hackeysackthreaded.py", line 58, in runithackysacker(`i`,circle)File ".hackeysackthreaded.py", line 14, in __init__thread.start_new_thread(self.messageLoop,()) error: can't start new thread
4.3 stackless
import stackless import random import sysclass hackysacker:counter = 0def __init__(self,name,circle):self.name = nameself.circle = circlecircle.append(self)self.channel = stackless.channel()stackless.tasklet(self.messageLoop)()def incrementCounter(self):hackysacker.counter += 1if hackysacker.counter >= turns:while self.circle:self.circle.pop().channel.send('exit')def messageLoop(self):while 1:message = self.channel.receive()if message == 'exit':returndebugPrint("%s got hackeysack from %s" % (self.name, message.name))kickTo = self.circle[random.randint(0,len(self.circle)-1)]while kickTo is self:kickTo = self.circle[random.randint(0,len(self.circle)-1)]debugPrint("%s kicking hackeysack to %s" % (self.name, kickTo.name))self.incrementCounter()kickTo.channel.send(self)def debugPrint(x):if debug:print xdebug = 5 hackysackers = 5 turns = 1def runit(hs=5,ts=5,dbg=1):global hackysackers,turns,debughackysackers = hsturns = tsdebug = dbghackysacker.counter = 0circle = []one = hackysacker('1',circle)for i in range(hackysackers):hackysacker(`i`,circle)one.channel.send(one)try:stackless.run()except TaskletExit:passif __name__ == "__main__":runit()
以上代码实质上与线程版本是等价的,主要区别仅在于我们使用微进程来代替线程,并且使用通道代替Queue来进行切换。让我们运行它,并检查输出:
C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:Python24python.exe hackysackstackless.py 1 got hackeysack from 1 1 kicking hackeysack to 1 1 got hackeysack from 1 1 kicking hackeysack to 4 4 got hackeysack from 1 4 kicking hackeysack to 1 1 got hackeysack from 4 1 kicking hackeysack to 4 4 got hackeysack from 1 4 kicking hackeysack to 0
C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:Python24python.exe c:Python24libtimeit.py -s"import hackysackstackless" hackysackstackless.runit(10,1000,0) 100 loops, best of 3: 19.7 msec per loop
其仅用了19.7毫秒,速度几乎是线程版本的10倍。现在我们同样开始增加微线程的数量:
C:Documents and SettingsgrantDesktopwhy_stacklesscode>c:Python24python.exe c:Python24libtimeit.py -s"import hackysackstackless" hackysackstackless.runit(100,1000,0) 100 loops, best of 3: 19.7 msec per loopC:Documents and SettingsgrantDesktopwhy_stacklesscode>c:Python24python.exe c:Python24libtimeit.py -s"import hackysackstackless" hackysackstackless.runit(1000,1000,0) 10 loops, best of 3: 26.9 msec per loopC:Documents and SettingsgrantDesktopwhy_stacklesscode>c:Python24python.exe c:Python24libtimeit.py -s"import hackysackstackless" hackysackstackless.runit(10000,1000,0) 10 loops, best of 3: 109 msec per loopC:Documents and SettingsgrantDesktopwhy_stacklesscode>c:Python24python.exe c:Python24libtimeit.py -s"import hackysackstackless" hackysackstackless.runit(100000,1000,0) 10 loops, best of 3: 1.07 sec per loop
甚至直到10,000个线程的时候,那时线程版本早已不能运行了,而这个仍然可以比线程版本在10个线程的时候运行的还快。
4.4 总结
Stackless Python并发式编程介绍相关推荐
- Python的异步编程介绍(MD)
概要 Git:Python的异步编程介绍(MD). 博客 博客地址:IT老兵驿站 前言 这里翻译和学习一篇介绍Python的异步编程的文章,在网上找了半天,感觉这篇写的很好,把几种实现方案都举了例子, ...
- Python的异步编程介绍
前言 这里翻译和学习一篇介绍Python的异步编程的文章,在网上找了半天,感觉这篇写的很好,把几种实现方案都举了例子,而且列出了优劣. 正文 Introduction Asynchronous pro ...
- python的socket编程_Python Socket编程详细介绍
在使用Python做socket编程时,由于需要使用阻塞(默认)的方式来读取数据流,此时对于数据的结束每次都需要自己处理,太麻烦.并且网上也没找到太好的封装,所以就自己写了个简单的封装. 封装思路 1 ...
- 《Python游戏趣味编程》 第1章 Python与开发环境介绍
图书简介可以参考这里: 童晶:<Python游戏趣味编程>新书上架了 要编写Python代码.让计算机读懂Python程序,我们需要安装Python集成开发环境.读者可以打开Python官 ...
- 《Python数据可视化编程实战》——5.5 用OpenGL制作动画
本节书摘来异步社区<Python数据可视化编程实战>一书中的第5章,第5.5节,作者:[爱尔兰]Igor Milovanović,更多章节内容可以访问云栖社区"异步社区" ...
- PYTHON黑帽编程1.5 使用WIRESHARK练习网络协议分析
Python黑帽编程1.5 使用Wireshark练习网络协议分析 1.5.0.1 本系列教程说明 本系列教程,采用的大纲母本为<Understanding Network Hacks At ...
- Python黑帽编程2.4 流程控制
Python黑帽编程2.4 流程控制 本节要介绍的是Python编程中和流程控制有关的关键字和相关内容. 2.4.1 if -..else 先上一段代码: #!/usr/bin/python # - ...
- Python面对对象编程——对象、类详解及实例
Python中类与对象的初认识 1.Python 面向对象 Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对象是很容易的.本章节我们将详细介绍Python的 ...
- python采用函数式编程模式吗_Python函数与函数式编程
1 函数 函数是组织好的,可重复使用的,用来实现单一,或相关联功能的代码段. 函数能提高应用的模块性,和代码的重复利用率.你已经知道Python提供了许多内建函数,比如print().但你也可以自己创 ...
最新文章
- PAT Basic 1072
- 伯克利AI研究院:强化学习是GPT2等自回归语言模型弥补不足的良方?
- 复习最基础的linux 之 创建用户及修改用户组
- 微信小程序一些常见的坑
- matlab中如何提取等高线,在Python或MATLAB中从等高线图中提取数据
- TestNG或JUnit
- Qt中使用QSqlDatabase::removeDatabase()的正确方法
- mac下hive-1.2.2-src版本的编译
- 使用layui 做后台管理界面,在Tab中的链接点击后添加一个新TAB的解决方法
- 解决Navicat无法连接到MySQL的问题
- 李迅雷+老龄化下中青年人消费心态的变化的角度来看未来十倍股
- 生成树模型 matlab,最小生成树matlab
- html肤质测试,皮肤致敏试验
- 一口一个超酥脆的宝宝小零食,超简单的做法哦
- Corolado软件峰会关于Geronimo的介绍
- SpringBoot项目的云服务器部署
- 孙陶然:有能力的第二个标准是业绩好
- 三种常见mq的优缺点比较
- AudioEffect源码解析
- Fira Code字体中增加思源黑体支持中文字体