python多线程队列处理_Python线程和队列使用的一点思考
Python線程和隊列使用的一點思考
1. 斗哥采訪環節請問為什么要使用線程?
答:為了提高程序速度,代碼效率呀。
請問為什么要使用隊列?
答:個人認為隊列可以保證線程安全,實現線程間的同步,比較穩。
線程為什么采用Threading模塊?
答:據我所知還有Thread模塊,該模塊級別較低不推薦用。更高級別的是threading模塊,它有一個Thread類,而且提供了各種非常好用的同步機制。
你所說的同步機制是指啥?
答:就是希望線程能夠同時開跑,想象一下“所有的馬同時沖出柵欄”的場景,就是我們說的同步了,而Therad模塊的同步機制不佳亦是其不推薦使用的原因之一。
2. 需要用到線程的場景?
2.1 舉個簡單的案例,假設這么一個需求如下
給定200個IP地址,可能開放端口有80,443,7001,7002,8000,8080,8081,8888,9000,9001等,現需以'[[http://ip:port](http://ip:port)]([http://ip:port](http://ip:port))'形式訪問頁面以判斷是否正常。
2.2 為什么要用線程解決這個需求?
200個ip地址和10個端口,累計請求2000次,一個個請求過去太慢,設定線程可以提高效率。
2.3 如果不用線程怎么樣實現?
(以下僅為演示代碼,如有錯誤敬請指出)**注:**將200個ip地址放到ip.txt記事本中,讀取ip拼接端口并請求。#-*-coding:utf-8
import requests
portlist=[80,443,7001,7002,8000,8080,8081,8888,9000,9001]
ips=[t.replace("\n","") for t in open('ip.txt',"r").readlines()]
for ip in ips:
for port in portlist:
url="http://"+ip+':'+str(port)
try:
resp=requests.get(url=url,timeout=2)
print url,"mabey normal..."
except:
print url,"unknown wrong..."
注:運行上述代碼,請求2000條url,每條等待超時2秒,差不多要1個多小時才能跑完,漫長的等待過程中漸漸失去笑容和耐心……
3. threading如何運用以解決上述問題?
使用threading模塊的Thread類來創建線程,先要創建一個Thread的實例,傳給它一個函數去跑線程。比如專門定義一個函數req()來請求URL,然后把這個req函數傳給Thread的實例,接著開啟線程……可以先看下面這段代碼。(以下代碼修改自上文)import requests
import threading
def req(url): #請求的代碼寫成一個函數
try:
resp=requests.get(url=url,timeout=2)
print url,"mabey normal..."
except:
print url,"unknown wrong..."
def main():
portlist=[80,443,7001,7002,8000,8080,8081,8888,9000,9001]
ips=[t.replace("\n","") for t in open('ip.txt',"r").readlines()]
urllist=[]
threads=[]
for ip in ips: #將url寫到列表中
for port in portlist:
urllist.append("http://"+ip+':'+str(port))
for url in urllist: #將線程存到threads列表中
t=threading.Thread(target=req,args=(url,))
threads.append(t)
for t in threads: #開始跑線程,用while來控制線程數
t.start()
while True:
if(len(threading.enumerate())<100):
break
if __name__ == '__main__':
main()
其中, t=threading.Thread(target=req,args=(url,))的t就是一個Thread的實例了,args是可以加入到函數傳遞的參數,而本代碼的req()函數需要傳遞參數是url。
你可以看到的是,這個代碼建立了2000個未開始跑的線程放到threads列表里,接著遍歷threads來開啟線程。為了防止線程數過多,用while循環判斷如果當前線程數len(threading.enumerate()超過了100則不開啟下一個線程,也就是100指的是線程數。
3.1 簡單評價下這個腳本
(有其他建議請留言評論)代碼效果:線程設置成100,不到1分鐘時間就跑完了整個腳本。
為了方便,將url寫到了列表里,付出的代價是浪費了相應的內存空間。
線程數的控制使用while循環和threading.enumerate()來判斷,不夠優雅。
3.2 更好一點的方式:使用for循環來控制線程數+while循環結合列表的pop方法import requests
import threading
def req():
while True:
try:
url=urllist.pop()
except IndexError:
break
try:
resp=requests.get(url=url,timeout=2)
print url,"mabey normal..."
except:
print url,"unknown wrong..."
def main():
for i in range(10):
t=threading.Thread(target=req)
t.start()
for i in range(10):
t.join()
if __name__ == '__main__':
portlist=[80,443,7001,7002,8000,8080,8081,8888,9000,9001]
ips=[t.replace("\n","") for t in open('ip.txt',"r").readlines()]
urllist=[]
for ip in ips:
for port in portlist:
urllist.append("http://"+ip+':'+str(port))
main()
你可以發現上述代碼大概有2點變化。線程的開啟更加純粹,不再有傳遞參數的功能。而多了個for循環來執行t.join(),這個是用來阻塞主線程,當開啟的子線程未跑完時,主線程不往下繼續執行。
參數url的獲取,改成了url=urllist.pop()的方式,因為我們知道列表的pop方法會默認每次從列表移除最后一個元素并返回該元素的值,所以能夠起到參數獲取的作用。線程數的控制用for i in range(10)來開啟,而不用while循環不停去檢測線程數是不是超了。而參數獲取完成了之后,列表也空了,似乎達到節省了空間,不過我們還是得事先準備一個列表,把url一個個預先填進去(如下圖)。
如果不希望暫用那么大的空間,那么我們需要有一個緩存空間,并發的存入且能夠并發讀取而且不會發生阻塞,腦補一張圖大概長下面這樣:
上圖描述就是人們常說的做生產者和消費者模式。在python中,Queue模塊實現了多生產者多消費者隊列, 尤其適合多線程編程.Queue類中實現了所有需要的鎖原語,可以優雅的解決上述的問題,那么首先需要了解一下關于隊列的一些細節……
4. 隊列幾點介紹
4.1 導入import Queue
from Queue import [Queue Class]
4.2 通用方法put(item(,block[,timeout]))從隊列中放入item。
get()從隊列移除并返回一個數據。(這個方法和列表的pop()方法是不是很像?)
empty()如果隊列為空,返回True,反之返回False
task_done()task_done()告訴隊列,get()方法的任務處理完畢。
join()阻塞調用線程,直到隊列中的所有任務被處理掉。
4.3 隊列模型(類)FIFO隊列(First in First Out,先進先出)
class Queue.Queue(maxsize=0)
Queue提供了一個基本的FIFO容器,maxsize是個整數,指明了隊列中能存放的數據個數的上限。一旦達到上限,插入會導致阻塞,直到隊列中的數據被消費掉。如果maxsize小于或者等于0,隊列大小沒有限制。import Queue
q=Queue.Queue
for i in range(1,6):
q.put(i)
while not q.empty():
print q.get()
[console]
$ python queth.py
1
2
3
4
5
更多用法參考官方文檔:Queue官方文檔
4.4 多線程和Queue.Queue()
前面已經提到,參數的獲取可以并發的實現,但是苦于一直沒有找到合適的場景。我們在文章中提到的需求,你可以發現2000個url的獲取通過個循環就可以輕易獲取根本用不到生產者的模式,也就提現不出隊列的強大,盡管如此我還是給出對應的腳本,你可以發現其實和用列表獲取參數的差別并不大。(小伙伴有更好的場景歡迎提出來一起討論呀)import requests
import threading
from Queue import Queue
def req(queue):
while True:
url=queue.get()
try:
resp=requests.get(url=url,timeout=2)
queue.task_done()
print url,"mabey normal..."
except:
print url,"unknown wrong..."
queue.task_done()
def get_url(queue):
portlist=[80,443,7001,7002,8000,8080,8081,8888,9000,9001]
ips=[t.replace("\n","") for t in open('ip.txt',"r").readlines()]
for ip in ips:
for port in portlist:
url="http://"+ip+':'+str(port)
queue.put(url,1)
def main():
queue=Queue()
get_url(queue)
for i in range(10):
t=threading.Thread(target=req,args=(queue,))
t.setDaemon(True)
t.start()
queue.join()
if __name__ == '__main__':
main()
你可以發現通過一個get_url()函數就輕易將url存儲到隊列中,我們在定義queue的時候是可以設定隊列空間大小的,如queue=Queue(100),當存放了100個元素而未被取走時,隊列會處于阻塞狀態。不過設定隊列大小上述代碼就需要改寫了,可以參考《Python核心編程》關于線程和隊列的章節。
5. 小結
以上就是本次關于線程和隊列思考的全部內容了,希望能夠幫助到那些剛入門python的新手玩家們。本文也僅限斗哥的一點點小思考,也希望大家能夠提出更好的見解和斗哥一起討論。(The End)
總結
以上是生活随笔為你收集整理的python多线程队列处理_Python线程和队列使用的一点思考的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: OnexPlayer壹号掌机mini版正
- 下一篇: python 字符编码处理_浅析Pyth