關(guān)于臭名昭著的"GIL"嚴(yán)重阻礙了python多線程在實(shí)際工程上的應(yīng)用,很多人理直氣壯的說:“為什么要用python里面的多線程,它的速度比單線程還慢”。實(shí)際上,這句話對了一半,在計(jì)算密集型任務(wù)中,由于GIL的存在,即使你的電腦是多核超棒,但是任意一個時刻只能有一個線程在運(yùn)行,WTF!但是很多人就會問了,既然python的多線程這么菜,那么它存在的意義是什么呢?為了增強(qiáng)本文的實(shí)用性,我不會糾結(jié)原理,而是介紹一種優(yōu)雅的方法使用python里面的多線程。
我們會在主要做I/O操作的,最典型的就是網(wǎng)絡(luò)連接的時候去使用python里面的多進(jìn)程,我們可以創(chuàng)建幾百個這樣的線程,當(dāng)有的線程在請求網(wǎng)絡(luò)連接的時候,其他線程還可以繼續(xù)處理數(shù)據(jù)。當(dāng)然對于密集型計(jì)算,我還是會多選擇多線程并發(fā),這也不是我們這篇文章講述的內(nèi)容了。
說起并發(fā),多線程之類的,很多人總是拿出什么生產(chǎn)者和消費(fèi)者之類的問題,對此我想說,我完全不想知道這些,知道這些能有什么用呢,去參加操作系統(tǒng)考試嗎?我們要優(yōu)雅,不要污。
加入我們遇到了這樣的一個任務(wù),你有成百上千的url,你想把對應(yīng)的網(wǎng)頁下載下來,我相信研究過爬蟲的同學(xué)肯定會遇到這個問題。我會說我曾經(jīng)爬下了豆瓣妹子里面所有妹子的性感照片嗎,需要的話可以聯(lián)系我。為了使問題簡單化,我們假設(shè)這些url已經(jīng)被你知道了。
urls = [
'http://baidu.com.com',
'http://dataunion.org/21885.html',
'http://blog.jobbole.com/98480/',
'http://m.itdecent.cn/users/c5fde99df3f2/latest_articles',
]
urls = urls*100 #生成400個url
Example 1:使用傳統(tǒng)的單線程去下載這些網(wǎng)頁
import time
import requests
def download(url):
try:
return requests.get(url,timeout=0.7) #timeout這個參數(shù)很有必要,加入你不慎去訪問google等我國禁止的網(wǎng)站,那么程序在嘗試0.7秒鐘連接不上的時候,那么會放棄
except:
pass
start = time.time()
responses = [download(url) for url in urls]
html = [response.text for response in responses if hasattr(response,"text")]
end = time.time()
print ("Time: %f seconds" % (end - start))
Time: 169.916672 seconds
我們提取了400個網(wǎng)頁里面的文字(其實(shí)只下載了將近300個,有100個網(wǎng)頁被我們因?yàn)樵谥付〞r間內(nèi)連接不上,我們放棄了),在單線程的情況下,差不多花了170秒,但是我們的實(shí)際工程中,隨時可能面對的是幾十萬個網(wǎng)頁信息的搜索,這個時間就很驚人了。
我們想要優(yōu)雅的使用python多線程,我們需要借助tomorrow這個庫,只需要一句話,就可以讓你的代碼完成速度的蛻變!
Example 1:使用多線程去下載這些網(wǎng)頁
import time
import requests
from tomorrow import threads
@threads(100) #我們開一百個線程去搞定我們的下載任務(wù)
def download(url):
try:
return requests.get(url,timeout=0.7) #timeout這個參數(shù)很有必要,加入你不慎去訪問google等我國禁止的網(wǎng)站,那么程序在嘗試0.7秒鐘連接不上的時候,那么會放棄
except:
pass
start = time.time()
responses = [download(url) for url in urls]
html = [response.text for response in responses if hasattr(response,"text")]
end = time.time()
print ("Time: %f seconds" % (end - start))
Time: 11.314254 seconds
對比結(jié)果簡直震撼,后一個版本的代碼只是比前一個單線程的代碼多了一行代碼,速度卻帶來了15倍的提升。
有木有興趣來試一試呢?