Ⅰ python基礎(21)-線程通信
到這里,我們要聊一下線程通信的內容;
首先,我們拋開語言不談,先看看比較基礎的東西,線程間通信的方式;其實也就是哪幾種(我這里說的,是我的所謂的知道的。。。)事件,消息隊列,信號量,條件變數(鎖算不算?我只是認為是同步的一種);所以我們也就是要把這些掌握了,因為各有各的好處嘛;
條件變數我放到了上面的線程同步裡面講了,我總感覺這算是同步的一種,沒有很多具體信息的溝通;同時吧,我認為條件變數比較重要,因為這種可以應用於線程池的操作上;所以比較重要;這里,拋開條件變數不談,我們看看其他的東西;
1、消息隊列:
queue 模塊下提供了幾個阻塞隊列,這些隊列主要用於實現線程通信。在 queue 模塊下主要提供了三個類,分別代表三種隊列,它們的主要區別就在於進隊列、出隊列的不同。
關於這三個隊列類的簡單介紹如下:
queue.Queue(maxsize=0):代表 FIFO(先進先出)的常規隊列,maxsize 可以限制隊列的大小。如果隊列的大小達到隊列的上限,就會加鎖,再次加入元素時就會被阻塞,直到隊列中的元素被消費。如果將 maxsize 設置為 0 或負數,則該隊列的大小就是無限制的。
queue.LifoQueue(maxsize=0):代表 LIFO(後進先出)的隊列,與 Queue 的區別就是出隊列的順序不同。
PriorityQueue(maxsize=0):代表優先順序隊列,優先順序最小的元素先出隊列。
這三個隊列類的屬性和方法基本相同, 它們都提供了如下屬性和方法:
Queue.qsize():返回隊列的實際大小,也就是該隊列中包含幾個元素。
Queue.empty():判斷隊列是否為空。
Queue.full():判斷隊列是否已滿。
Queue.put(item, block=True, timeout=None):向隊列中放入元素。如果隊列己滿,且 block 參數為 True(阻塞),當前線程被阻塞,timeout 指定阻塞時間,如果將 timeout 設置為 None,則代表一直阻塞,直到該隊列的元素被消費;如果隊列己滿,且 block 參數為 False(不阻塞),則直接引發 queue.FULL 異常。
Queue.put_nowait(item):向隊列中放入元素,不阻塞。相當於在上一個方法中將 block 參數設置為 False。
Queue.get(item, block=True, timeout=None):從隊列中取出元素(消費元素)。如果隊列已滿,且 block 參數為 True(阻塞),當前線程被阻塞,timeout 指定阻塞時間,如果將 timeout 設置為 None,則代表一直阻塞,直到有元素被放入隊列中; 如果隊列己空,且 block 參數為 False(不阻塞),則直接引發 queue.EMPTY 異常。
Queue.get_nowait(item):從隊列中取出元素,不阻塞。相當於在上一個方法中將 block 參數設置為 False。
其實我們想想,這個隊列,是python進行封裝的,那麼我們可以用在線程間的通信;同時也是可以用做一個數據結構;先進先出就是隊列,後進先出就是棧;我們用這個棧寫個十進制轉二進制的例子:
沒毛病,可以正常的列印;其中需要注意的就是,maxsize在初始化的時候如果是0或者是個負數的話,那麼就會是不限制大小;
那麼其實我們想想,我們如果用做線程通信的話,我們兩個線程,可以把隊列設置為1的大小,如果是1對多,比如是創建者和消費者的關系,我們完全可以作為消息隊列,比如說創建者一直在創建一些東西,然後放入到消息隊列裡面,然後供消費著使用;就是一個很好的例子;所以,其實說是消息隊列,也就是隊列,沒差;
=====================================================================
下面來看一下事件
Event 是一種非常簡單的線程通信機制,一個線程發出一個 Event,另一個線程可通過該 Event 被觸發。
Event 本身管理一個內部旗標,程序可以通過 Event 的 set() 方法將該旗標設置為 True,也可以調用 clear() 方法將該旗標設置為 False。程序可以調用 wait() 方法來阻塞當前線程,直到 Event 的內部旗標被設置為 True。
Event 提供了如下方法:
is_set():該方法返回 Event 的內部旗標是否為True。
set():該方法將會把 Event 的內部旗標設置為 True,並喚醒所有處於等待狀態的線程。
clear():該方法將 Event 的內部旗標設置為 False,通常接下來會調用 wait() 方法來阻塞當前線程。
wait(timeout=None):該方法會阻塞當前線程。
這里我想解釋一下;其實對於事件來說,事件可以看成和條件變數是一樣的,只是我們說說不一樣的地方;
1、對於事件來說,一旦觸發了事件,也就是說,一旦set為true了,那麼就會一直為true,需要clear調內部的標志,才能繼續wait;但是conditon不是,他是一次性的喚醒其他線程;
2、conditon自己帶鎖;事件呢?不是的;沒有自己的鎖;比如說有一個存錢的線程,有一個是取錢的線程;那麼存錢的線程要存錢;需要怎麼辦呢?1、發現銀行沒有錢了(is_set判斷);2、鎖住銀行;3、存錢;4、釋放銀行;5、喚醒事件;對於取錢的人;1、判斷是否有錢;2、被喚醒了,然後鎖住銀行;3、開始取錢;4、清理告訴存錢的人,我沒錢了(clear);5、釋放鎖;6、等著錢存進去;
其實說白了,就是記住一點;這個旗標需要自己clear就對了
寫個例子,怕以後忘了怎麼用;
其實時間和信號量比較像;但是信號量不用自己清除標志位;但是事件是需要的;
Ⅱ Python能做什麼,能夠開發什麼項目
Python是一種計算機程序設計語言。是一種面向對象的動態類型語言,最初被設計用於編寫自動化腳本(shell),隨著版本的不斷更新和語言新功能的添加,越來越多被用於獨立的、大型項目的開發。
Python是一種解釋型腳本語言,可以應用於Web 和 Internet開發、科學計算和統計、人工智慧、教育、桌面界面開發、軟體開發、後端開發這些領域。
Python的應用
1、系統編程
提供API(Application Programming Interface應用程序編程介面),能方便進行系統維護和管理,Linux下標志性語言之一,是很多系統管理員理想的編程工具。
2、圖形處理
有PIL、Tkinter等圖形庫支持,能方便進行圖形處理。
3、數學處理
NumPy擴展提供大量與許多標准數學庫的介面。
4、文本處理
python提供的re模塊能支持正則表達式,還提供SGML,XML分析模塊,許多程序員利用python進行XML程序的開發。
5、資料庫編程
程序員可通過遵循Python DB-API(資料庫應用程序編程介面)規范的模塊與Microsoft SQL Server,Oracle,Sybase,DB2,MySQL、SQLite等資料庫通信。python自帶有一個Gadfly模塊,提供了一個完整的SQL環境。
6、網路編程
提供豐富的模塊支持sockets編程,能方便快速地開發分布式應用程序。很多大規模軟體開發計劃例如Zope,Mnet 及BitTorrent. Google都在廣泛地使用它。
7、Web編程
應用的開發語言,支持最新的XML技術。
8、多媒體應用
Python的PyOpenGL模塊封裝了「OpenGL應用程序編程介面」,能進行二維和三維圖像處理。PyGame模塊可用於編寫游戲軟體。
9、pymo引擎
PYMO全稱為python memories off,是一款運行於Symbian S60V3,Symbian3,S60V5, Symbian3, Android系統上的AVG游戲引擎。因其基於python2.0平台開發,並且適用於創建秋之回憶(memories off)風格的AVG游戲,故命名為PYMO。
10、黑客編程
python有一個hack的庫,內置了你熟悉的或不熟悉的函數,但是缺少成就感。
Ⅲ Python與java如何構建通信請給出一個學習方向。
1、python提供restful介面,java直接調用restful api即可;
2、rpc通信,現成框架例如grpc。 java 和python都支持。
3、消息隊列,python計算結果存到消息隊列,java端訂閱處理即可。相當於引入一個中間代理角色。
思路大概就上面的方式,根據實際場景進行技術選型。
Ⅳ 如何在後台部署深度學習模型
搭建深度學習後台伺服器
我們的Keras深度學習REST API將能夠批量處理圖像,擴展到多台機器(包括多台web伺服器和Redis實例),並在負載均衡器之後進行循環調度。
為此,我們將使用:
KerasRedis(內存數據結構存儲)
Flask (Python的微web框架)
消息隊列和消息代理編程範例
本篇文章的整體思路如下:
我們將首先簡要討論Redis數據存儲,以及如何使用它促進消息隊列和消息代理。然後,我們將通過安裝所需的Python包來配置Python開發環境,以構建我們的Keras深度學習REST API。一旦配置了開發環境,就可以使用Flask web框架實現實際的Keras深度學習REST API。在實現之後,我們將啟動Redis和Flask伺服器,然後使用cURL和Python向我們的深度學習API端點提交推理請求。最後,我們將以對構建自己的深度學習REST API時應該牢記的注意事項的簡短討論結束。
第一部分:簡要介紹Redis如何作為REST API消息代理/消息隊列
測試和原文的命令一致。
第三部分:配置Python開發環境以構建Keras REST API
文章中說需要創建新的虛擬環境來防止影響系統級別的python項目(但是我沒有創建),但是還是需要安裝rest api所需要依賴的包。以下為所需要的包。
第四部分:實現可擴展的Keras REST API
首先是Keras Redis Flask REST API數據流程圖
讓我們開始構建我們的伺服器腳本。為了方便起見,我在一個文件中實現了伺服器,但是它可以按照您認為合適的方式模塊化。為了獲得最好的結果和避免復制/粘貼錯誤,我建議您使用本文的「下載」部分來獲取相關的腳本和圖像。
為了簡單起見,我們將在ImageNet數據集上使用ResNet預訓練。我將指出在哪裡可以用你自己的模型交換ResNet。flask模塊包含flask庫(用於構建web API)。redis模塊將使我們能夠與redis數據存儲介面。從這里開始,讓我們初始化將在run_keras_server.py中使用的常量.
我們將向伺服器傳遞float32圖像,尺寸為224 x 224,包含3個通道。我們的伺服器可以處理一個BATCH_SIZE = 32。如果您的生產系統上有GPU(s),那麼您需要調優BATCH_SIZE以獲得最佳性能。我發現將SERVER_SLEEP和CLIENT_SLEEP設置為0.25秒(伺服器和客戶端在再次輪詢Redis之前分別暫停的時間)在大多數系統上都可以很好地工作。如果您正在構建一個生產系統,那麼一定要調整這些常量。
讓我們啟動我們的Flask app和Redis伺服器:
在這里你可以看到啟動Flask是多麼容易。在運行這個伺服器腳本之前,我假設Redis伺服器正在運行(之前的redis-server)。我們的Python腳本連接到本地主機6379埠(Redis的默認主機和埠值)上的Redis存儲。不要忘記將全局Keras模型初始化為None。接下來我們來處理圖像的序列化:
Redis將充當伺服器上的臨時數據存儲。圖像將通過諸如cURL、Python腳本甚至是移動應用程序等各種方法進入伺服器,而且,圖像只能每隔一段時間(幾個小時或幾天)或者以很高的速率(每秒幾次)進入伺服器。我們需要把圖像放在某個地方,因為它們在被處理前排隊。我們的Redis存儲將作為臨時存儲。
為了將圖像存儲在Redis中,需要對它們進行序列化。由於圖像只是數字數組,我們可以使用base64編碼來序列化圖像。使用base64編碼還有一個額外的好處,即允許我們使用JSON存儲圖像的附加屬性。
base64_encode_image函數處理序列化。類似地,在通過模型傳遞圖像之前,我們需要反序列化圖像。這由base64_decode_image函數處理。
預處理圖片
我已經定義了一個prepare_image函數,它使用Keras中的ResNet50實現對輸入圖像進行預處理,以便進行分類。在使用您自己的模型時,我建議修改此函數,以執行所需的預處理、縮放或規范化。
從那裡我們將定義我們的分類方法
classify_process函數將在它自己的線程中啟動,我們將在下面的__main__中看到這一點。該函數將從Redis伺服器輪詢圖像批次,對圖像進行分類,並將結果返回給客戶端。
在model = ResNet50(weights="imagenet")這一行中,我將這個操作與終端列印消息連接起來——根據Keras模型的大小,載入是即時的,或者需要幾秒鍾。
載入模型只在啟動這個線程時發生一次——如果每次我們想要處理一個映像時都必須載入模型,那麼速度會非常慢,而且由於內存耗盡可能導致伺服器崩潰。
載入模型後,這個線程將不斷輪詢新的圖像,然後將它們分類(注意這部分代碼應該時尚一部分的繼續)
在這里,我們首先使用Redis資料庫的lrange函數從隊列(第79行)中獲取最多的BATCH_SIZE圖像。
從那裡我們初始化imageIDs和批處理(第80和81行),並開始在第84行開始循環隊列。
在循環中,我們首先解碼對象並將其反序列化為一個NumPy數組image(第86-88行)。
接下來,在第90-96行中,我們將向批處理添加圖像(或者如果批處理當前為None,我們將該批處理設置為當前圖像)。
我們還將圖像的id附加到imageIDs(第99行)。
讓我們完成循環和函數
在這個代碼塊中,我們檢查批處理中是否有圖像(第102行)。如果我們有一批圖像,我們通過模型(第105行)對整個批進行預測。從那裡,我們循環一個圖像和相應的預測結果(110-122行)。這些行向輸出列表追加標簽和概率,然後使用imageID將輸出存儲在Redis資料庫中(第116-122行)。
我們使用第125行上的ltrim從隊列中刪除了剛剛分類的圖像集。最後,我們將睡眠設置為SERVER_SLEEP時間並等待下一批圖像進行分類。下面我們來處理/predict我們的REST API端點
稍後您將看到,當我們發布到REST API時,我們將使用/predict端點。當然,我們的伺服器可能有多個端點。我們使用@app。路由修飾符以第130行所示的格式在函數上方定義端點,以便Flask知道調用什麼函數。我們可以很容易地得到另一個使用AlexNet而不是ResNet的端點,我們可以用類似的方式定義具有關聯函數的端點。你懂的,但就我們今天的目的而言,我們只有一個端點叫做/predict。
我們在第131行定義的predict方法將處理對伺服器的POST請求。這個函數的目標是構建JSON數據,並將其發送回客戶機。如果POST數據包含圖像(第137和138行),我們將圖像轉換為PIL/Pillow格式,並對其進行預處理(第141-143行)。
在開發這個腳本時,我花了大量時間調試我的序列化和反序列化函數,結果發現我需要第147行將數組轉換為C-contiguous排序(您可以在這里了解更多)。老實說,這是一個相當大的麻煩事,但我希望它能幫助你站起來,快速跑。
如果您想知道在第99行中提到的id,那麼實際上是使用uuid(通用唯一標識符)在第151行生成的。我們使用UUID來防止hash/key沖突。
接下來,我們將圖像的id和base64編碼附加到d字典中。使用rpush(第153行)將這個JSON數據推送到Redis db非常簡單。
讓我們輪詢伺服器以返回預測
我們將持續循環,直到模型伺服器返回輸出預測。我們開始一個無限循環,試圖得到157-159條預測線。從這里,如果輸出包含預測,我們將對結果進行反序列化,並將結果添加到將返回給客戶機的數據中。我們還從db中刪除了結果(因為我們已經從資料庫中提取了結果,不再需要將它們存儲在資料庫中),並跳出了循環(第163-172行)。
否則,我們沒有任何預測,我們需要睡覺,繼續投票(第176行)。如果我們到達第179行,我們已經成功地得到了我們的預測。在本例中,我們向客戶機數據添加True的成功值(第179行)。注意:對於這個示例腳本,我沒有在上面的循環中添加超時邏輯,這在理想情況下會為數據添加一個False的成功值。我將由您來處理和實現。最後我們稱燒瓶。jsonify對數據,並將其返回給客戶端(第182行)。這就完成了我們的預測函數。
為了演示我們的Keras REST API,我們需要一個__main__函數來實際啟動伺服器
第186-196行定義了__main__函數,它將啟動classify_process線程(第190-192行)並運行Flask應用程序(第196行)。
第五部分:啟動可伸縮的Keras REST API
要測試我們的Keras深度學習REST API,請確保使用本文的「下載」部分下載源代碼示例圖像。從這里,讓我們啟動Redis伺服器,如果它還沒有運行:
然後,在另一個終端中,讓我們啟動REST API Flask伺服器:
另外,我建議在向伺服器提交請求之前,等待您的模型完全載入到內存中。現在我們可以繼續使用cURL和Python測試伺服器。
第七部分:使用cURL訪問Keras REST API
使用cURL來測試我們的Keras REST API伺服器。這是我的家庭小獵犬Jemma。根據我們的ResNet模型,她被歸類為一隻擁有94.6%自信的小獵犬。
你會在你的終端收到JSON格式的預測:
第六部分:使用Python向Keras REST API提交請求
如您所見,使用cURL驗證非常簡單。現在,讓我們構建一個Python腳本,該腳本將發布圖像並以編程方式解析返回的JSON。
讓我們回顧一下simple_request.py
我們在這個腳本中使用Python請求來處理向伺服器提交數據。我們的伺服器運行在本地主機上,可以通過埠5000訪問端點/predict,這是KERAS_REST_API_URL變數(第6行)指定的。
我們還定義了IMAGE_PATH(第7行)。png與我們的腳本在同一個目錄中。如果您想測試其他圖像,請確保指定到您的輸入圖像的完整路徑。
讓我們載入圖像並發送到伺服器:
我們在第10行以二進制模式讀取圖像並將其放入有效負載字典。負載通過請求發送到伺服器。在第14行發布。如果我們得到一個成功消息,我們可以循環預測並將它們列印到終端。我使這個腳本很簡單,但是如果你想變得更有趣,你也可以使用OpenCV在圖像上繪制最高的預測文本。
第七部分:運行簡單的請求腳本
編寫腳本很容易。打開終端並執行以下命令(當然,前提是我們的Flask伺服器和Redis伺服器都在運行)。
使用Python以編程方式使用我們的Keras深度學習REST API的結果
第八部分:擴展深度學習REST API時的注意事項
如果您預期在深度學習REST API上有較長一段時間的高負載,那麼您可能需要考慮一種負載平衡演算法,例如循環調度,以幫助在多個GPU機器和Redis伺服器之間平均分配請求。
記住,Redis是內存中的數據存儲,所以我們只能在隊列中存儲可用內存中的盡可能多的圖像。
使用float32數據類型的單個224 x 224 x 3圖像將消耗602112位元組的內存。
Ⅳ python入門需要學哪些
初學者學習Python需循序漸進,可以從以下內容入手學習:
1.Python基礎知識
學習任何一門編程語言都需要學習相關語法知識,Python基礎知識的學習主要包括Python解釋器執行原理、字元編碼、注釋、變數、縮進、流程式控制制、文件操作、數據類型、數據類型內置方法、字元串格式化、運算符、輸入輸出、三元運算、collections、列表、字典、元組、集合、IO操作、文件增刪改查、函數等。
5.資料庫、緩存、隊列
Python資料庫、緩存、隊列學習內容為Python操作redis、Python操作memcache、rabbitMQ消息隊列、資料庫介紹、mysql資料庫安裝使用、mysql管理、mysql數據類型、常用mysql命令、創建資料庫、外鍵、增刪改查表、許可權、事務、索引、Python操作mysql等。
6.Web開發基礎
Python之Web開發基礎學習內容為HTML基礎、CSS基礎、JavaScript基礎、局部變數和全局變數、集合、數組、字典、函數參數、原型、面向對象、作用域、dom編程、jquery介紹、jquery選擇器、jquery屬性和CSS操作、jquery文檔處理、jquery篩選、jquery事件託管、jquery事件、jquery ajax、jquery擴展方法、bootstrap使用、EasyUI介紹和使用等。
7.Web框架學習
Python之Web框架學習內容為Web框架本質、socket伺服器、基於反射的路由系統、WSGI介凱閉行紹及原理實現態御、開發自己的Web框架、MVC和MTV、路由系統、模板、django基礎學習與使用、普通路由和動態路由、模板引擎、ORM介紹、Django ORM增刪改查學習、自定義tag、django進階學習與使用、模型綁定、Form表單驗證、Django ORM進階學習、ModelForm、自定義Validator等。
Ⅵ Python中Flask框架的變數和函數
可以從模板中直接訪問Flask當前的config對象:
就是flask中代表當前請求的request對象:
輸出結果為(本機本地運行):
為Flask的session對象
{{ session.get('name') }}
輸出結果為(本機本地運行):檔蠢
url_for會緩運根據傳入的路由器函數名,返回該路由對應的URL,在模板中始終使用url_for()就可以安全的修改路由綁定的URL,則不比擔心模板中渲染出錯的鏈接,經常用於超鏈接,比如下面的例子:
點擊即可跳轉至主頁
同時,如果定義的路由URL是帶有參數的,可以把它們作為關鍵字參數傳入url_for(),Flask會把他們填擾蠢梁充進最終生成的URL中:
這個函數會返回之前在flask中通過flask()傳入的消息的列表,flash函數可以把由Python字元串表示的消息加入一個消息隊列中,再使用get_flashed_message()函數取出它們並消費掉:
Ⅶ 一篇文章帶你深度解析Python線程和進程
使用Python中的線程模塊,能夠同時運行程序的不同部分,並簡化設計。如果你已經入門Python,並且想用線程來提升程序運行速度的話,希望這篇教程會對你有所幫助。
線程與進程
什麼是進程
進程是系統進行資源分配和調度的一個獨立單位 進程是具有一定獨立功能的程序關於某個數據集合上的一次運行活動,進程是系統進行資源分配和調度的一個獨立單位。每個進程都有自己的獨立內存空間,不同進程通過進程間通信來通信。由於進程比較重量,占據獨立的內存,所以上下文進程間的切換開銷(棧、寄存器、虛擬內存、文件句柄等)比較大,但相對比較穩定安全。
什麼是線程
CPU調度和分派的基本單位 線程是進程的一個實體,是CPU調度和分派的基本單位,它是比進程更小的能獨立運行的基本單位.線程自己基本上不擁有系統資源,只擁有一點在運行中必不可少的資源(如程序計數器,一組寄存器和棧),但是它可與同屬一個進程的其他的線程共享進程所擁有的全部資源。線程間通信主要通過共享內存,上下文切換很快,資源開銷較少,但相比進程不夠穩定容易丟失數據。
進程與線程的關系圖
線程與進程的區別:
進程
現實生活中,有很多的場景中的事情是同時進行的,比如開車的時候 手和腳共同來駕駛 汽車 ,比如唱歌跳舞也是同時進行的,再比如邊吃飯邊打電話;試想如果我們吃飯的時候有一個領導來電,我們肯定是立刻就接聽了。但是如果你吃完飯再接聽或者回電話,很可能會被開除。
注意:
多任務的概念
什麼叫 多任務 呢?簡單地說,就是操作系統可以同時運行多個任務。打個比方,你一邊在用瀏覽器上網,一邊在聽MP3,一邊在用Word趕作業,這就是多任務,至少同時有3個任務正在運行。還有很多任務悄悄地在後台同時運行著,只是桌面上沒有顯示而已。
現在,多核CPU已經非常普及了,但是,即使過去的單核CPU,也可以執行多任務。由於CPU執行代碼都是順序執行的,那麼,單核CPU是怎麼執行多任務的呢?
答案就是操作系統輪流讓各個任務交替執行,任務1執行0.01秒,切換到任務2,任務2執行0.01秒,再切換到任務3,執行0.01秒,這樣反復執行下去。表面上看,每個任務都是交替執行的,但是,由於CPU的執行速度實在是太快了,我們感覺就像所有任務都在同時執行一樣。
真正的並行執行多任務只能在多核CPU上實現,但是,由於任務數量遠遠多於CPU的核心數量,所以,操作系統也會自動把很多任務輪流調度到每個核心上執行。 其實就是CPU執行速度太快啦!以至於我們感受不到在輪流調度。
並行與並發
並行(Parallelism)
並行:指兩個或兩個以上事件(或線程)在同一時刻發生,是真正意義上的不同事件或線程在同一時刻,在不同CPU資源呢上(多核),同時執行。
特點
並發(Concurrency)
指一個物理CPU(也可以多個物理CPU) 在若幹道程序(或線程)之間多路復用,並發性是對有限物理資源強制行使多用戶共享以提高效率。
特點
multiprocess.Process模塊
process模塊是一個創建進程的模塊,藉助這個模塊,就可以完成進程的創建。
語法:Process([group [, target [, name [, args [, kwargs]]]]])
由該類實例化得到的對象,表示一個子進程中的任務(尚未啟動)。
注意:1. 必須使用關鍵字方式來指定參數;2. args指定的為傳給target函數的位置參數,是一個元祖形式,必須有逗號。
參數介紹:
group:參數未使用,默認值為None。
target:表示調用對象,即子進程要執行的任務。
args:表示調用的位置參數元祖。
kwargs:表示調用對象的字典。如kwargs = {'name':Jack, 'age':18}。
name:子進程名稱。
代碼:
除了上面這些開啟進程的方法之外,還有一種以繼承Process的方式開啟進程的方式:
通過上面的研究,我們千方百計實現了程序的非同步,讓多個任務可以同時在幾個進程中並發處理,他們之間的運行沒有順序,一旦開啟也不受我們控制。盡管並發編程讓我們能更加充分的利用IO資源,但是也給我們帶來了新的問題。
當多個進程使用同一份數據資源的時候,就會引發數據安全或順序混亂問題,我們可以考慮加鎖,我們以模擬搶票為例,來看看數據安全的重要性。
加鎖可以保證多個進程修改同一塊數據時,同一時間只能有一個任務可以進行修改,即串列的修改。加鎖犧牲了速度,但是卻保證了數據的安全。
因此我們最好找尋一種解決方案能夠兼顧:1、效率高(多個進程共享一塊內存的數據)2、幫我們處理好鎖問題。
mutiprocessing模塊為我們提供的基於消息的IPC通信機制:隊列和管道。隊列和管道都是將數據存放於內存中 隊列又是基於(管道+鎖)實現的,可以讓我們從復雜的鎖問題中解脫出來, 我們應該盡量避免使用共享數據,盡可能使用消息傳遞和隊列,避免處理復雜的同步和鎖問題,而且在進程數目增多時,往往可以獲得更好的可獲展性( 後續擴展該內容 )。
線程
Python的threading模塊
Python 供了幾個用於多線程編程的模塊,包括 thread, threading 和 Queue 等。thread 和 threading 模塊允許程序員創建和管理線程。thread 模塊 供了基本的線程和鎖的支持,而 threading 供了更高級別,功能更強的線程管理的功能。Queue 模塊允許用戶創建一個可以用於多個線程之間 共享數據的隊列數據結構。
python創建和執行線程
創建線程代碼
1. 創建方法一:
2. 創建方法二:
進程和線程都是實現多任務的一種方式,例如:在同一台計算機上能同時運行多個QQ(進程),一個QQ可以打開多個聊天窗口(線程)。資源共享:進程不能共享資源,而線程共享所在進程的地址空間和其他資源,同時,線程有自己的棧和棧指針。所以在一個進程內的所有線程共享全局變數,但多線程對全局變數的更改會導致變數值得混亂。
代碼演示:
得到的結果是:
首先需要明確的一點是GIL並不是Python的特性,它是在實現Python解析器(CPython)時所引入的一個概念。就好比C++是一套語言(語法)標准,但是可以用不同的編譯器來編譯成可執行代碼。同樣一段代碼可以通過CPython,PyPy,Psyco等不同的Python執行環境來執行(其中的JPython就沒有GIL)。
那麼CPython實現中的GIL又是什麼呢?GIL全稱Global Interpreter Lock為了避免誤導,我們還是來看一下官方給出的解釋:
主要意思為:
因此,解釋器實際上被一個全局解釋器鎖保護著,它確保任何時候都只有一個Python線程執行。在多線程環境中,Python 虛擬機按以下方式執行:
由於GIL的存在,Python的多線程不能稱之為嚴格的多線程。因為 多線程下每個線程在執行的過程中都需要先獲取GIL,保證同一時刻只有一個線程在運行。
由於GIL的存在,即使是多線程,事實上同一時刻只能保證一個線程在運行, 既然這樣多線程的運行效率不就和單線程一樣了嗎,那為什麼還要使用多線程呢?
由於以前的電腦基本都是單核CPU,多線程和單線程幾乎看不出差別,可是由於計算機的迅速發展,現在的電腦幾乎都是多核CPU了,最少也是兩個核心數的,這時差別就出來了:通過之前的案例我們已經知道,即使在多核CPU中,多線程同一時刻也只有一個線程在運行,這樣不僅不能利用多核CPU的優勢,反而由於每個線程在多個CPU上是交替執行的,導致在不同CPU上切換時造成資源的浪費,反而會更慢。即原因是一個進程只存在一把gil鎖,當在執行多個線程時,內部會爭搶gil鎖,這會造成當某一個線程沒有搶到鎖的時候會讓cpu等待,進而不能合理利用多核cpu資源。
但是在使用多線程抓取網頁內容時,遇到IO阻塞時,正在執行的線程會暫時釋放GIL鎖,這時其它線程會利用這個空隙時間,執行自己的代碼,因此多線程抓取比單線程抓取性能要好,所以我們還是要使用多線程的。
GIL對多線程Python程序的影響
程序的性能受到計算密集型(CPU)的程序限制和I/O密集型的程序限制影響,那什麼是計算密集型和I/O密集型程序呢?
計算密集型:要進行大量的數值計算,例如進行上億的數字計算、計算圓周率、對視頻進行高清解碼等等。這種計算密集型任務雖然也可以用多任務完成,但是花費的主要時間在任務切換的時間,此時CPU執行任務的效率比較低。
IO密集型:涉及到網路請求(time.sleep())、磁碟IO的任務都是IO密集型任務,這類任務的特點是CPU消耗很少,任務的大部分時間都在等待IO操作完成(因為IO的速度遠遠低於CPU和內存的速度)。對於IO密集型任務,任務越多,CPU效率越高,但也有一個限度。
當然為了避免GIL對我們程序產生影響,我們也可以使用,線程鎖。
Lock&RLock
常用的資源共享鎖機制:有Lock、RLock、Semphore、Condition等,簡單給大家分享下Lock和RLock。
Lock
特點就是執行速度慢,但是保證了數據的安全性
RLock
使用鎖代碼操作不當就會產生死鎖的情況。
什麼是死鎖
死鎖:當線程A持有獨占鎖a,並嘗試去獲取獨占鎖b的同時,線程B持有獨占鎖b,並嘗試獲取獨占鎖a的情況下,就會發生AB兩個線程由於互相持有對方需要的鎖,而發生的阻塞現象,我們稱為死鎖。即死鎖是指多個進程因競爭資源而造成的一種僵局,若無外力作用,這些進程都將無法向前推進。
所以,在系統設計、進程調度等方面注意如何不讓這四個必要條件成立,如何確定資源的合理分配演算法,避免進程永久占據系統資源。
死鎖代碼
python線程間通信
如果各個線程之間各干各的,確實不需要通信,這樣的代碼也十分的簡單。但這一般是不可能的,至少線程要和主線程進行通信,不然計算結果等內容無法取回。而實際情況中要復雜的多,多個線程間需要交換數據,才能得到正確的執行結果。
python中Queue是消息隊列,提供線程間通信機制,python3中重名為為queue,queue模塊塊下提供了幾個阻塞隊列,這些隊列主要用於實現線程通信。
在 queue 模塊下主要提供了三個類,分別代表三種隊列,它們的主要區別就在於進隊列、出隊列的不同。
簡單代碼演示
此時代碼會阻塞,因為queue中內容已滿,此時可以在第四個queue.put('蘋果')後面添加timeout,則成為 queue.put('蘋果',timeout=1)如果等待1秒鍾仍然是滿的就會拋出異常,可以捕獲異常。
同理如果隊列是空的,無法獲取到內容默認也會阻塞,如果不阻塞可以使用queue.get_nowait()。
在掌握了 Queue 阻塞隊列的特性之後,在下面程序中就可以利用 Queue 來實現線程通信了。
下面演示一個生產者和一個消費者,當然都可以多個
使用queue模塊,可在線程間進行通信,並保證了線程安全。
協程
協程,又稱微線程,纖程。英文名Coroutine。
協程是python個中另外一種實現多任務的方式,只不過比線程更小佔用更小執行單元(理解為需要的資源)。為啥說它是一個執行單元,因為它自帶CPU上下文。這樣只要在合適的時機, 我們可以把一個協程 切換到另一個協程。只要這個過程中保存或恢復 CPU上下文那麼程序還是可以運行的。
通俗的理解:在一個線程中的某個函數,可以在任何地方保存當前函數的一些臨時變數等信息,然後切換到另外一個函數中執行,注意不是通過調用函數的方式做到的,並且切換的次數以及什麼時候再切換到原來的函數都由開發者自己確定。
在實現多任務時,線程切換從系統層面遠不止保存和恢復 CPU上下文這么簡單。操作系統為了程序運行的高效性每個線程都有自己緩存Cache等等數據,操作系統還會幫你做這些數據的恢復操作。所以線程的切換非常耗性能。但是協程的切換只是單純的操作CPU的上下文,所以一秒鍾切換個上百萬次系統都抗的住。
greenlet與gevent
為了更好使用協程來完成多任務,除了使用原生的yield完成模擬協程的工作,其實python還有的greenlet模塊和gevent模塊,使實現協程變的更加簡單高效。
greenlet雖說實現了協程,但需要我們手工切換,太麻煩了,gevent是比greenlet更強大的並且能夠自動切換任務的模塊。
其原理是當一個greenlet遇到IO(指的是input output 輸入輸出,比如網路、文件操作等)操作時,比如訪問網路,就自動切換到其他的greenlet,等到IO操作完成,再在適當的時候切換回來繼續執行。
模擬耗時操作:
如果有耗時操作也可以換成,gevent中自己實現的模塊,這時候就需要打補丁了。
使用協程完成一個簡單的二手房信息的爬蟲代碼吧!
以下文章來源於Python專欄 ,作者宋宋
文章鏈接:https://mp.weixin.qq.com/s/2r3_ipU3HjdA5VnqSHjUnQ