當前位置:編程學習大全網 - 編程語言 - python stackless 怎麽多線程並發

python stackless 怎麽多線程並發

1 介紹

1.1 為什麽要使用Stackless

摘自?stackless?網站。

Note

Stackless Python 是Python編程語言的壹個增強版本,它使程序員從基於線程的編程方式中獲得好處,並避免傳統線程所帶來的性能與復雜度問題。Stackless為 Python帶來的微線程擴展,是壹種低開銷、輕量級的便利工具,如果使用得當,可以獲益如下:

改進程序結構

增進代碼可讀性

提高編程人員生產力

以上是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 並發可能是(僅僅可能是)下壹個重要的編程範式

我個人相信,並發將是軟件世界裏的下壹個重要範式。隨著程序變得更加復雜和耗費資源,我們已經不能指望摩爾定律來每年給我們提供更快的CPU了,當 前,日常使用的個人計算機的性能提升來自於多核與多CPU機。壹旦單個CPU的性能達到極限,軟件開發者們將不得不轉向分布式模型,靠多臺計算機的互相協 作來建立強大的應用(想想GooglePlex)。為了取得多核機和分布式編程的優勢,並發將很快成為做事情的方式的事實標準。

1.2 安裝stackless

安裝Stackless的細節可以在其網站上找到。現在Linux用戶可以通過SubVersion取得源代碼並編譯;而對於Windows用戶, 則有壹個.zip文件供使用,需要將其解壓到現有的Python安裝目錄中。接下來,本教程假設Stackless Python已經安裝好了,可以工作,並且假設妳對Python語言本身有基本的了解。

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

>>>

註意:當調用?stackless.schedule()?的時候,當前活動微進程將暫停執行,並將自身重新插入到調度器隊列的末尾,好讓下壹個微進程被執行。壹旦在它前面的所有其他微進程都運行過了,它將從上次 停止的地方繼續開始運行。這個過程會持續,直到所有的活動微進程都完成了運行過程。這就是使用stackless達到合作式多任務的方式。

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 堆棧

我仔細考慮了,自己對C語言堆棧的細節究竟了解多少,最終還是決定完全不去講它。似乎,其他人對其所嘗試的描述,以及圖表,只有本身已經理解了的人才能看得懂。我將試著給出壹個最簡單的說明,而對其有更多興趣的讀者可以從網上查找更多信息。

每當壹個子例程被調用,都有壹個“棧幀”被建立,這是用來保存變量,以及其他子例程局部信息的區域。於是,當妳調用 ping() ,則有壹個棧幀被建立,來保存這次調用相關的信息。簡言之,這個幀記載著 ping 被調用了。當再調用 pong() ,則又建立了壹個棧幀,記載著 pong 也被調用了。這些棧幀是串聯在壹起的,每個子例程調用都是其中的壹環。就這樣,堆棧中顯示: ping 被調用所以 pong 接下來被調用。顯然,當 pong() 再調用 ping() ,則使堆棧再擴展。下面是個直觀的表示:

幀 堆棧

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 那麽,為什麽要使用堆棧?

上例是有意設計的,用來體現堆棧的問題所在。在大多數情況下,當每個子例程返回的時候,其棧幀將被清除掉,就是說堆棧將會自行實現清理過程。這壹般 來說是件好事,在C語言中,堆棧就是壹個不需要編程者來手動進行內存管理的區域。很幸運,Python程序員也不需要直接來擔心內存管理與堆棧。但是由於 Python解釋器本身也是用C實現的,那些實現者們可是需要擔心這個的。使用堆棧是會使事情方便,除非我們開始調用那種從不返回的函數,如上例中的,那 時候,堆棧的表現就開始和程序員別扭起來,並耗盡可用的內存。

3.2 走進協程

此時,將堆棧弄溢出是有點愚蠢的。 ping() 和 pong() 本不是真正意義的子例程,因為其中哪個也不從屬於另壹個,它們是“協程”,處於同等的地位,並可以彼此間進行無縫通信。

幀 堆棧

1 ping 被調用

2 pong 被調用

3 ping 被調用

4 pong 被調用

5 ping 被調用

6 pong 被調用

在stackless中,我們使用通道來建立協程。還記得嗎,通道所帶來的兩個好處中的壹個,就是能夠控制微進程之間運行的流程。使用通道,我們可以在 ping 和 pong 這兩個協程之間自由來回,要多少次就多少次,都不會堆棧溢出:

#

# pingpong_stackless.py

#

import stackless

ping_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()

妳可以運行這個程序要多久有多久,它都不會崩潰,且如果妳檢查其內存使用量(使用Windows的任務管理器或Linux的top命令),將會發現 使用量是恒定的。這個程序的協程版本,不管運行壹分鐘還是壹天,使用的內存都是壹樣的。而如果妳檢查原先那個遞歸版本的內存用量,則會發現其迅速增長,直 到崩潰。

3.3 總結

是否還記得,先前我提到過,那個代碼的遞歸版本,有經驗的程序員會壹眼看出毛病。但老實說,這裏面並沒有什麽“計算機科學”方面的原因在阻礙它的正 常工作,有些讓人堅信的東西,其實只是個與實現細節有關的小問題——只因為大多數傳統編程語言都使用堆棧。某種意義上說,有經驗的程序員都是被洗了腦,從 而相信這是個可以接受的問題。而stackless,則真正察覺了這個問題,並除掉了它。

4 輕量級線程

與當今的操作系統中內建的、和標準Python代碼中所支持的普通線程相比,“微線程”要更為輕量級,正如其名稱所暗示。它比傳統線程占用更少的內存,並且微線程之間的切換,要比傳統線程之間的切換更加節省資源。

為了準確說明微線程的效率究竟比傳統線程高多少,我們用兩者來寫同壹個程序。

4.1 hackysack模擬

Hackysack是壹種遊戲,就是壹夥臟乎乎的小子圍成壹個圈,來回踢壹個裝滿了豆粒的沙包,目標是不讓這個沙包落地,當傳球給別人的時候,可以耍各種把戲。踢沙包只可以用腳。

在我們的簡易模擬中,我們假設壹旦遊戲開始,圈裏人數就是恒定的,並且每個人都是如此厲害,以至於如果允許的話,這個遊戲可以永遠停不下來。

4.2 遊戲的傳統線程版本

import thread

import random

import sys

import Queue

class hackysacker:

counter = 0

def __init__(self,name,circle):

self.name = name

self.circle = circle

circle.append(self)

self.messageQueue = Queue.Queue()

thread.start_new_thread(self.messageLoop,())

def incrementCounter(self):

hackysacker.counter += 1

if 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 x

debug=1

hackysackers=5

turns = 5

  • 上一篇:編程squ
  • 下一篇:校園有哪些暴利生意?(無本小生意致富?)
  • copyright 2024編程學習大全網