- 資訊首頁(yè) > 開(kāi)發(fā)技術(shù) > 編程語(yǔ)言 >
- Python中多線(xiàn)程、多進(jìn)程、協(xié)程的區別是什么
今天就跟大家聊聊有關(guān)Python中多線(xiàn)程、多進(jìn)程、協(xié)程的區別是什么,可能很多人都不太了解,為了讓大家更加了解,小編給大家總結了以下內容,希望大家根據這篇文章可以有所收獲。
首先我們寫(xiě)一個(gè)簡(jiǎn)化的爬蟲(chóng),對各個(gè)功能細分,有意識進(jìn)行函數式編程。下面代碼的目的是訪(fǎng)問(wèn)300次百度頁(yè)面并返回狀態(tài)碼,其中parse_1函數可以設定循環(huán)次數,每次循環(huán)將當前循環(huán)數(從0開(kāi)始)和url傳入parse_2函數。
import requests def parse_1(): url = 'https://www.baidu.com' for i in range(300): parse_2(url) def parse_2(url): response = requests.get(url) print(response.status_code) if __name__ == '__main__': parse_1()
性能的消耗主要在IO請求中,當單進(jìn)程單線(xiàn)程模式下請求URL時(shí)必然會(huì )引起等待
示例代碼就是典型的串行邏輯,parse_1將url和循環(huán)數傳遞給parse_2,parse_2請求并返回狀態(tài)碼后parse_1繼續迭代一次,重復之前步驟
因為CPU在執行程序時(shí)每個(gè)時(shí)間刻度上只會(huì )存在一個(gè)線(xiàn)程,因此多線(xiàn)程實(shí)際上提高了進(jìn)程的使用率從而提高了CPU的使用率
實(shí)現多線(xiàn)程的庫有很多,這里用concurrent.futures中的ThreadPoolExecutor來(lái)演示。介紹ThreadPoolExecutor庫是因為它相比其他庫代碼更簡(jiǎn)潔
為了方便說(shuō)明問(wèn)題,下面代碼中如果是新增加的部分,代碼行前會(huì )加上 > 符號便于觀(guān)察說(shuō)明問(wèn)題,實(shí)際運行需要去掉
import requests > from concurrent.futures import ThreadPoolExecutor def parse_1(): url = 'https://www.baidu.com' # 建立線(xiàn)程池 > pool = ThreadPoolExecutor(6) for i in range(300): > pool.submit(parse_2, url) > pool.shutdown(wait=True) def parse_2(url): response = requests.get(url) print(response.status_code) if __name__ == '__main__': parse_1()
跟同步相對的就是異步。異步就是彼此獨立,在等待某事件的過(guò)程中繼續做自己的事,不需要等待這一事件完成后再工作。線(xiàn)程就是實(shí)現異步的一個(gè)方式,也就是說(shuō)多線(xiàn)程是異步處理異步就意味著(zhù)不知道處理結果,有時(shí)候我們需要了解處理結果,就可以采用回調
import requests from concurrent.futures import ThreadPoolExecutor # 增加回調函數 > def callback(future): > print(future.result()) def parse_1(): url = 'https://www.baidu.com' pool = ThreadPoolExecutor(6) for i in range(300): > results = pool.submit(parse_2, url) # 回調的關(guān)鍵步驟 > results.add_done_callback(callback) pool.shutdown(wait=True) def parse_2(url): response = requests.get(url) print(response.status_code) if __name__ == '__main__': parse_1()
Python實(shí)現多線(xiàn)程有一個(gè)無(wú)數人詬病的GIL(全局解釋器鎖),但多線(xiàn)程對于爬取網(wǎng)頁(yè)這種多數屬于IO密集型的任務(wù)依舊很合適。
多進(jìn)程用兩個(gè)方法實(shí)現:ProcessPoolExecutor和multiprocessing
1. ProcessPoolExecutor
和實(shí)現多線(xiàn)程的ThreadPoolExecutor類(lèi)似
import requests > from concurrent.futures import ProcessPoolExecutor def parse_1(): url = 'https://www.baidu.com' # 建立線(xiàn)程池 > pool = ProcessPoolExecutor(6) for i in range(300): > pool.submit(parse_2, url) > pool.shutdown(wait=True) def parse_2(url): response = requests.get(url) print(response.status_code) if __name__ == '__main__': parse_1()
可以看到改動(dòng)了兩次類(lèi)名,代碼依舊很簡(jiǎn)潔,同理也可以添加回調函數
import requests from concurrent.futures import ProcessPoolExecutor > def callback(future): > print(future.result()) def parse_1(): url = 'https://www.baidu.com' pool = ProcessPoolExecutor(6) for i in range(300): > results = pool.submit(parse_2, url) > results.add_done_callback(callback) pool.shutdown(wait=True) def parse_2(url): response = requests.get(url) print(response.status_code) if __name__ == '__main__': parse_1()
2. multiprocessing
直接看代碼,一切都在注釋中。
import requests > from multiprocessing import Pool def parse_1(): url = 'https://www.baidu.com' # 建池 > pool = Pool(processes=5) # 存放結果 > res_lst = [] for i in range(300): # 把任務(wù)加入池中 > res = pool.apply_async(func=parse_2, args=(url,)) # 獲取完成的結果(需要取出) > res_lst.append(res) # 存放最終結果(也可以直接存儲或者print) > good_res_lst = [] > for res in res_lst: # 利用get獲取處理后的結果 > good_res = res.get() # 判斷結果的好壞 > if good_res: > good_res_lst.append(good_res) # 關(guān)閉和等待完成 > pool.close() > pool.join() def parse_2(url): response = requests.get(url) print(response.status_code) if __name__ == '__main__': parse_1()
可以看到multiprocessing庫的代碼稍繁瑣,但支持更多的拓展。多進(jìn)程和多線(xiàn)程確實(shí)能夠達到加速的目的,但如果遇到IO阻塞會(huì )出現線(xiàn)程或者進(jìn)程的浪費,因此有一個(gè)更好的方法……
協(xié)程+回調配合動(dòng)態(tài)協(xié)作就可以達到異步非阻塞的目的,本質(zhì)只用了一個(gè)線(xiàn)程,所以很大程度利用了資源
實(shí)現異步非阻塞經(jīng)典是利用asyncio庫+yield,為了方便利用逐漸出現了更上層的封裝 aiohttp,要想更好的理解異步非阻塞最好還是深入了解asyncio庫。而gevent是一個(gè)非常方便實(shí)現協(xié)程的庫
import requests > from gevent import monkey # 猴子補丁是協(xié)作運行的靈魂 > monkey.patch_all() > import gevent def parse_1(): url = 'https://www.baidu.com' # 建立任務(wù)列表 > tasks_list = [] for i in range(300): > task = gevent.spawn(parse_2, url) > tasks_list.append(task) > gevent.joinall(tasks_list) def parse_2(url): response = requests.get(url) print(response.status_code) if __name__ == '__main__': parse_1()
gevent能很大提速,也引入了新的問(wèn)題:如果我們不想速度太快給造成太大負擔怎么辦?如果是多進(jìn)程多線(xiàn)程的建池方法,可以控制池內數量。如果用gevent想要控制速度也有一個(gè)不錯的方法:建立隊列。gevent中也提供了Quene類(lèi),下面代碼改動(dòng)較大
import requests from gevent import monkey monkey.patch_all() import gevent > from gevent.queue import Queue def parse_1(): url = 'https://www.baidu.com' tasks_list = [] # 實(shí)例化隊列 > quene = Queue() for i in range(300): # 全部url壓入隊列 > quene.put_nowait(url) # 兩路隊列 > for _ in range(2): > task = gevent.spawn(parse_2) > tasks_list.append(task) gevent.joinall(tasks_list) # 不需要傳入參數,都在隊列中 > def parse_2(): # 循環(huán)判斷隊列是否為空 > while not quene.empty(): # 彈出隊列 > url = quene.get_nowait() response = requests.get(url) # 判斷隊列狀態(tài) > print(quene.qsize(), response.status_code) if __name__ == '__main__': parse_1()
免責聲明:本站發(fā)布的內容(圖片、視頻和文字)以原創(chuàng )、來(lái)自本網(wǎng)站內容采集于網(wǎng)絡(luò )互聯(lián)網(wǎng)轉載等其它媒體和分享為主,內容觀(guān)點(diǎn)不代表本網(wǎng)站立場(chǎng),如侵犯了原作者的版權,請告知一經(jīng)查實(shí),將立刻刪除涉嫌侵權內容,聯(lián)系我們QQ:712375056,同時(shí)歡迎投稿傳遞力量。
Copyright ? 2009-2022 56dr.com. All Rights Reserved. 特網(wǎng)科技 特網(wǎng)云 版權所有 特網(wǎng)科技 粵ICP備16109289號
域名注冊服務(wù)機構:阿里云計算有限公司(萬(wàn)網(wǎng)) 域名服務(wù)機構:煙臺帝思普網(wǎng)絡(luò )科技有限公司(DNSPod) CDN服務(wù):阿里云計算有限公司 百度云 中國互聯(lián)網(wǎng)舉報中心 增值電信業(yè)務(wù)經(jīng)營(yíng)許可證B2
建議您使用Chrome、Firefox、Edge、IE10及以上版本和360等主流瀏覽器瀏覽本網(wǎng)站