1. 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 現實世界就是並發的
現實世界就是「並發」的,它是由一群事物(或「演員」)所組成,而這些事物以一種對彼此所知有限的、鬆散耦合的方式相互作用。傳說中面向對象編程有 一個好處,就是對象能夠對現實的世界進行模擬。這在一定程度上是正確的,面向對象編程很好地模擬了對象個體,但對於這些對象個體之間的交互,卻無法以一種 理想的方式來表現。例如,如下代碼實例,有什麼問題?
第一印象,沒問題。但是,上例中存在一個微妙的安排:所有事件是次序發生的,即:直到丈夫吃完飯,妻子才開始吃;兒子則一直等到母親吃完才吃;而女 兒則是最後一個。在現實世界中,哪怕是丈夫還堵車在路上,妻子、兒子和女兒仍然可以該吃就吃,而要在上例中的話,他們只能餓死了——甚至更糟:永遠沒有人 會知道這件事,因為他們永遠不會有機會拋出一個異常來通知這個世界!
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可調用對象(通常為函數或類的方法)來建立它,這將建立一個微進程並將其添加到調度器。這是一個快速演示:
注意,微進程將排起隊來,並不運行,直到調用stackless.run()。
2.2 調度器(scheler)
調度器控制各個微進程運行的順序。如果剛剛建立了一組微進程,它們將按照建立的順序來執行。在現實中,一般會建立一組可以再次被調度的微進程,好讓每個都有輪次機會。一個快速演示:
注意:當調用stackless.schele()的時候,當前活動微進程將暫停執行,並將自身重新插入到調度器隊列的末尾,好讓下一個微進程被執行。一旦在它前面的所有其他微進程都運行過了,它將從上次 停止的地方繼續開始運行。這個過程會持續,直到所有的活動微進程都完成了運行過程。這就是使用stackless達到合作式多任務的方式。
2.3 通道(channel)
通道使得微進程之間的信息傳遞成為可能。它做到了兩件事:
能夠在微進程之間交換信息。
能夠控制運行的流程。
又一個快速演示:
接收的微進程調用channel.receive()的時候,便阻塞住,這意味著該微進程暫停執行,直到有信息從這個通道送過來。除了往這個通道發送信息以外,沒有其他任何方式可以讓這個微進程恢復運行。
若有其他微進程向這個通道發送了信息,則不管當前的調度到了哪裡,這個接收的微進程都立即恢復執行;而發送信息的微進程則被轉移到調度列表的末尾,就像調用了stackless.schele()一樣。
同樣注意,發送信息的時候,若當時沒有微進程正在這個通道上接收,也會使當前微進程阻塞:
發送信息的微進程,只有在成功地將數據發送到了另一個微進程之後,才會重新被插入到調度器中。
2.4 總結
以上涵蓋了stackless的大部分功能。似乎不多是吧?——我們只使用了少許對象,和大約四五個函數調用,來進行操作。但是,使用這種簡單的API作為基本建造單元,我們可以開始做一些真正有趣的事情。
3 協程(coroutine)
3.1 子常式的問題
大多數傳統編程語言具有子常式的概念。一個子常式被另一個常式(可能還是其它某個常式的子常式)所調用,或返回一個結果,或不返回結果。從定義上說,一個子常式是從屬於其調用者的。
見下例:
有經驗的編程者會看到這個程序的問題所在:它導致了堆棧溢出。如果運行這個程序,它將顯示一大堆討厭的跟蹤信息,來指出堆棧空間已經耗盡。
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 這兩個協程之間自由來回,要多少次就多少次,都不會堆棧溢出:
你可以運行這個程序要多久有多久,它都不會崩潰,且如果你檢查其內存使用量(使用Windows的任務管理器或Linux的top命令),將會發現 使用量是恆定的。這個程序的協程版本,不管運行一分鍾還是一天,使用的內存都是一樣的。而如果你檢查原先那個遞歸版本的內存用量,則會發現其迅速增長,直 到崩潰。
3.3 總結
是否還記得,先前我提到過,那個代碼的遞歸版本,有經驗的程序員會一眼看出毛病。但老實說,這裡面並沒有什麼「計算機科學」方面的原因在阻礙它的正 常工作,有些讓人堅信的東西,其實只是個與實現細節有關的小問題——只因為大多數傳統編程語言都使用堆棧。某種意義上說,有經驗的程序員都是被洗了腦,從 而相信這是個可以接受的問題。而stackless,則真正察覺了這個問題,並除掉了它。
4 輕量級線程
與當今的操作系統中內建的、和標准Python代碼中所支持的普通線程相比,「微線程」要更為輕量級,正如其名稱所暗示。它比傳統線程佔用更少的內存,並且微線程之間的切換,要比傳統線程之間的切換更加節省資源。
為了准確說明微線程的效率究竟比傳統線程高多少,我們用兩者來寫同一個程序。
4.1 hackysack模擬
Hackysack是一種游戲,就是一夥臟乎乎的小子圍成一個圈,來回踢一個裝滿了豆粒的沙包,目標是不讓這個沙包落地,當傳球給別人的時候,可以耍各種把戲。踢沙包只可以用腳。
在我們的簡易模擬中,我們假設一旦游戲開始,圈裡人數就是恆定的,並且每個人都是如此厲害,以至於如果允許的話,這個游戲可以永遠停不下來。
4.2 游戲的傳統線程版本
2. 如何使用Python實現並發編程
多線程幾乎是每一個程序猿在使用每一種語言時都會首先想到用於解決並發的工具(JS程序員請迴避),使用多線程可以有效的利用CPU資源(Python例外)。然而多線程所帶來的程序的復雜度也不可避免,尤其是對競爭資源的同步問題。
然而在python中由於使用了全局解釋鎖(GIL)的原因,代碼並不能同時在多核上並發的運行,也就是說,Python的多線程不能並發,很多人會發現使用多線程來改進自己的Python代碼後,程序的運行效率卻下降了,這是多麼蛋疼的一件事呀!如果想了解更多細節,推薦閱讀這篇文章。實際上使用多線程的編程模型是很困難的,程序員很容易犯錯,這並不是程序員的錯誤,因為並行思維是反人類的,我們大多數人的思維是串列(精神分裂不討論),而且馮諾依曼設計的計算機架構也是以順序執行為基礎的。所以如果你總是不能把你的多線程程序搞定,恭喜你,你是個思維正常的程序猿:)
Python提供兩組線程的介面,一組是thread模塊,提供基礎的,低等級(Low Level)介面,使用Function作為線程的運行體。還有一組是threading模塊,提供更容易使用的基於對象的介面(類似於Java),可以繼承Thread對象來實現線程,還提供了其它一些線程相關的對象,例如Timer,Lock
使用thread模塊的例子
import thread
def worker():
"""thread worker function"""
print 'Worker'
thread.start_new_thread(worker)
使用threading模塊的例子
import threading
def worker():
"""thread worker function"""
print 'Worker'
t = threading.Thread(target=worker)
t.start()
或者Java Style
import threading
class worker(threading.Thread):
def __init__(self):
pass
def run():
"""thread worker function"""
print 'Worker'
t = worker()
t.start()
3. 如何在Python中編寫並發程序
GIL
在Python中,由於歷史原因(GIL),使得Python中多線程的效果非常不理想.GIL使得任何時刻Python只能利用一個CPU核,並
且它的調度演算法簡單粗暴:多線程中,讓每個線程運行一段時間t,然後強行掛起該線程,繼而去運行其他線程,如此周而復始,直到所有線程結束.
這使得無法有效利用計算機系統中的"局部性",頻繁的線程切換也對緩存不是很友好,造成資源的浪費.
據說Python官方曾經實現了一個去除GIL的Python解釋器,但是其效果還不如有GIL的解釋器,遂放棄.後來Python官方推出了"利
用多進程替代多線程"的方案,在Python3中也有concurrent.futures這樣的包,讓我們的程序編寫可以做到"簡單和性能兼得".
多進程/多線程+Queue
一般來說,在Python中編寫並發程序的經驗是:計算密集型任務使用多進程,IO密集型任務使用多進程或者多線程.另外,因為涉及到資源共享,所
以需要同步鎖等一系列麻煩的步驟,代碼編寫不直觀.另外一種好的思路是利用多進程/多線程+Queue的方法,可以避免加鎖這樣麻煩低效的方式.
現在在Python2中利用Queue+多進程的方法來處理一個IO密集型任務.
假設現在需要下載多個網頁內容並進行解析,單進程的方式效率很低,所以使用多進程/多線程勢在必行.
我們可以先初始化一個tasks隊列,裡面將要存儲的是一系列dest_url,同時開啟4個進程向tasks中取任務然後執行,處理結果存儲在一個results隊列中,最後對results中的結果進行解析.最後關閉兩個隊列.
下面是一些主要的邏輯代碼.
# -*- coding:utf-8 -*-
#IO密集型任務
#多個進程同時下載多個網頁
#利用Queue+多進程
#由於是IO密集型,所以同樣可以利用threading模塊
import multiprocessing
def main():
tasks = multiprocessing.JoinableQueue()
results = multiprocessing.Queue()
cpu_count = multiprocessing.cpu_count() #進程數目==CPU核數目
create_process(tasks, results, cpu_count) #主進程馬上創建一系列進程,但是由於阻塞隊列tasks開始為空,副進程全部被阻塞
add_tasks(tasks) #開始往tasks中添加任務
parse(tasks, results) #最後主進程等待其他線程處理完成結果
def create_process(tasks, results, cpu_count):
for _ in range(cpu_count):
p = multiprocessing.Process(target=_worker, args=(tasks, results)) #根據_worker創建對應的進程
p.daemon = True #讓所有進程可以隨主進程結束而結束
p.start() #啟動
def _worker(tasks, results):
while True: #因為前面所有線程都設置了daemon=True,故不會無限循環
try:
task = tasks.get() #如果tasks中沒有任務,則阻塞
result = _download(task)
results.put(result) #some exceptions do not handled
finally:
tasks.task_done()
def add_tasks(tasks):
for url in get_urls(): #get_urls() return a urls_list
tasks.put(url)
def parse(tasks, results):
try:
tasks.join()
except KeyboardInterrupt as err:
print "Tasks has been stopped!"
print err
while not results.empty():
_parse(results)
if __name__ == '__main__':
main()
利用Python3中的concurrent.futures包
在Python3中可以利用concurrent.futures包,編寫更加簡單易用的多線程/多進程代碼.其使用感覺和Java的concurrent框架很相似(借鑒?)
比如下面的簡單代碼示例
def handler():
futures = set()
with concurrent.futures.ProcessPoolExecutor(max_workers=cpu_count) as executor:
for task in get_task(tasks):
future = executor.submit(task)
futures.add(future)
def wait_for(futures):
try:
for future in concurrent.futures.as_completed(futures):
err = futures.exception()
if not err:
result = future.result()
else:
raise err
except KeyboardInterrupt as e:
for future in futures:
future.cancel()
print "Task has been canceled!"
print e
return result
總結
要是一些大型Python項目也這般編寫,那麼效率也太低了.在Python中有許多已有的框架使用,使用它們起來更加高效.
4. 求python書籍推薦
關於Python,程序江湖裡從不缺少金句:「人生苦短,我用Python!」「學完Python,便可上天!」,而最近這些話從調侃正在變為事實!
上周,PYPL(編程語言受歡迎程度) 四月官方榜單已發布,Python榮獲NO.1,相比去年 4 月份,今年上漲了 5.2%,成績頗為亮眼,從去年開始,Python就開始霸佔榜單長達1年,成為編程市場上份額最高的語言!
推薦第一本書籍:python安裝+基礎入門+全面實戰
書名:《Python3破冰人工智慧:從入門到實戰》
今天小編帶來了4本Python和python實戰方面的書籍,根據自身條件及感興趣的方向,選擇喜歡的書籍進行學習,如果你喜歡,歡迎點贊分享。
5. 請問怎麼學習Python
這是Python的入門階段,也是幫助零基礎學員打好基礎的重要階段。你需要掌握Python基本語法規則及變數、邏輯控制、內置數據結構、文件操作、高級函數、模塊、常用標准庫模板、函數、異常處理、mysql使用、協程等知識點。
學習目標:掌握Python的基本語法,具備基礎的編程能力;掌握Linux基本操作命令,掌握MySQL進階內容,完成銀行自動提款機系統實戰、英漢詞典、歌詞解析器等項目。
這一部分主要學習web前端相關技術,你需要掌握html、cssJavaScript、JQuery、Bootstrap、web開發基礎、Vue、FIask Views、FIask模板、資料庫操作、FIask配置等知識。
學習目標:掌握web前端技術內容,掌握web後端框架,熟練使用FIask、Tornado、Django,可以完成數據監控後台的項目。
這部分主要是學習爬蟲相關的知識點,你需要掌握數據抓取、數據提取、數據存儲、爬蟲並發、動態網頁抓取、scrapy框架、分布式爬蟲、爬蟲攻防、數據結構、演算法等知識。
學習目標:可以掌握爬蟲、數據採集,數據機構與演算法進階和人工智慧技術。可以完成爬蟲攻防、圖片馬賽克、電影推薦系統、地震預測、人工智慧項目等階段項目。
這是Python高級知識點,你需要學習項目開發流程、部署、高並發、性能調優、Go語言基礎、區塊鏈入門等內容。
學習目標:可以掌握自動化運維與區塊鏈開發技術,可以完成自動化運維項目、區塊鏈等項目。
按照上面的Python學習路線圖學習完後,你基本上就可以成為一名合格的Python開發工程師。當然,想要快速成為企業競聘的精英人才,你需要有好的老師指導,還要有較多的項目積累實戰經驗。
6. 如何提高python的運行效率
優化代碼邏輯及演算法。
使用高級技巧如生成器等。
使用多進程、多線程、協程等技術或一些並發庫。
將部分模塊用c/c++重新然後使用python調用,也可以用cython等。
7. 如何用Python一門語言通吃高性能並發,GPU計算和深度學習
第一個就是並發本身所帶來的開銷即新開處理線程、關閉處理線程、多個處理線程時間片輪轉所帶來的開銷。
實際上對於一些邏輯不那麼復雜的場景來說這些開銷甚至比真正的處理邏輯部分代碼的開銷更大。所以我們決定採用基於協程的並發方式,即服務進程只有一個(單cpu)所有的請求數據都由這個服務進程內部來維護,同時服務進程自行調度不同請求的處理順序,這樣避免了傳統多線程並發方式新建、銷毀以及系統調度處理線程的開銷。基於這樣的考慮我們選擇了基於Tornado框架實現api服務的開發。Tornado的實現非常簡潔明了,使用python的生成器作為協程,利用IOLoop實現了調度隊列。
第二個問題是資料庫的性能,這里說的資料庫包括MongoDB和Redis,我這里分開講。
先講MongoDB的問題,MongoDB主要存儲不同的用戶對於驗證的不同設置,比如該顯示什麼樣的圖片。
一開始每次驗證請求都會查詢MongoDB,當時我們的MongoDB是純內存的,同時三台機器組成一個復制集,這樣的組合大概能穩定承載八九千的qps,後來隨著我們驗證量越來越大,這個承載能力逐漸就成為了我們的瓶頸。
為了徹底搞定這個問題,我們提出了最極端的解決方案,乾脆直接把資料庫中的數據完全緩存到服務進程里定期批量更新,這樣查詢的開銷將大大降低。但是因為我們用的是Python,由於GIL的存在,在8核伺服器上會fork出來8個服務進程,進程之間不像線程那麼方便,所以我們基於mmap自己寫了一套夥伴演算法構建了一個跨進程共享緩存。自從這套緩存上線之後,Mongodb的負載幾乎變成了零。
說完了MongoDB再說Redis的問題,Redis代碼簡潔、數據結構豐富、性能強大,唯一的問題是作為一個單進程程序,終究性能是有上限的。
雖然今年Redis發布了官方的集群版本,但是經過我們的測試,認為這套分布式方案的故障恢復時間不夠優秀並且運維成本較高。在Redis官方集群方案面世之前,開源世界有不少proxy方案,比如Twtter的TwemProxy和豌豆莢的Codis。這兩種方案測試完之後給我們的感覺TwemProxy運維還是比較麻煩,Codis使用起來讓人非常心曠神怡,無論是修改配置還是擴容都可以在配置頁面上完成,並且性能也還算不錯,但無奈當時Codis還有比較嚴重的BUG只能放棄之。
幾乎嘗試過各種方案之後,我們還是下決心自己實現一套分布式方案,目的是高度貼合我們的需求並且運維成本要低、擴容要方便、故障切換要快最重要的是數據冗餘一定要做好。
基於上面的考慮,我們確定基於客戶端的分布式方案,通過zookeeper來同步狀態保證高可用。具體來說,我們修改Redis源碼,使其向zookeeper注冊,客戶端由zookeeper上獲取Redis伺服器集群信息並根據統一的一致性哈希演算法來計算數據應該存儲在哪台Redis上,並在哈希環的下一台Redis上寫入一份冗餘數據,當讀取原始數據失敗時可以立即嘗試讀取冗餘數據而不會造成服務中斷。
8. Python 常用的標准庫以及第三方庫有哪些
並發庫: gevent, 將一個個小功能用greenlet實現,然後通過queue, AsyncResult通信來進行解耦,起碼解決我現在遇到的大部分業務流程問題,而且擴展性超好。