Python多線程,thread标準庫。都說Python的多線程是雞肋,推薦使用多程序。
Python為了安全考慮有一個GIL。每個CPU在同一時間隻能執行一個線程
GIL的全稱是Global Interpreter Lock(全局解釋器鎖),就相當于通行證,每一次線程會先要去申請通行證,通行證申請下來了,才能進入CPU執行。
每個線程的執行方式:
-
1、擷取GIL
2、執行代碼直到sleep或者是python虛拟機将其挂起。
-
3、釋放GIL
每次釋放GIL鎖,線程進行鎖競争、切換線程,會消耗資源。并且由于GIL鎖存在,python裡一個程序永遠隻能同時執行一個線程(拿到GIL的線程才能執行),這就是為什麼在多核CPU上,python的多線程效率并不高。
下面使用多線程加隊列做的一個demo。爬取的是筆趣閣的小說,隻是做了一個列印,沒有做具體的儲存。爬取用的selenium。Chrome的無頭模式。有點慢,可以直接用庫,或者跑整站的話用scrapy.
使用Threading子產品建立線程,直接從threading.Thread繼承,然後重寫init方法和run方法:
線程同步
如果多個線程共同對某個資料修改,則可能出現不可預料的結果,為了保證資料的正确性,需要對多個線程進行同步。
使用Thread對象的Lock和Rlock可以實作簡單的線程同步,這兩個對象都有acquire方法和release方法,對于那些需要每次隻允許一個線程操作的資料,可以将其操作放到acquire和release方法之間。如下:
多線程的優勢在于可以同時運作多個任務(至少感覺起來是這樣)。但是當線程需要共享資料時,可能存在資料不同步的問題。
考慮這樣一種情況:一個清單裡所有元素都是0,線程”set”從後向前把所有元素改成1,而線程”print”負責從前往後讀取清單并列印。
那麼,可能線程”set”開始改的時候,線程”print”便來列印清單了,輸出就成了一半0一半1,這就是資料的不同步。為了避免這種情況,引入了鎖的概念。
鎖有兩種狀态——鎖定和未鎖定。每當一個線程比如”set”要通路共享資料時,必須先獲得鎖定;如果已經有别的線程比如”print”獲得鎖定了,那麼就讓線程”set”暫停,也就是同步阻塞;等到線程”print”通路完畢,釋放鎖以後,再讓線程”set”繼續。
經過這樣的處理,列印清單時要麼全部輸出0,要麼全部輸出1,不會再出現一半0一半1的尴尬場面。
線程優先級隊列
Python的Queue子產品中提供了同步的、線程安全的隊列類,包括FIFO(先入先出)隊列Queue,LIFO(後入先出)隊列LifoQueue,和優先級隊列PriorityQueue。這些隊列都實作了鎖原語,能夠在多線程中直接使用。可以使用隊列來實作線程間的同步。
Queue子產品中的常用方法:
- Queue.qsize() 傳回隊列的大小
- Queue.empty() 如果隊列為空,傳回True,反之False
- Queue.full() 如果隊列滿了,傳回True,反之False
- Queue.full 與 maxsize 大小對應
- Queue.get([block[, timeout]])擷取隊列,timeout等待時間
- Queue.get_nowait() 相當Queue.get(False)
- Queue.put(item) 寫入隊列,timeout等待時間
- Queue.put_nowait(item) 相當Queue.put(item, False)
- Queue.task_done() 在完成一項工作之後,Queue.task_done()函數向任務已經完成的隊列發送一個信号
- Queue.join() 實際上意味着等到隊列為空,再執行别的操作
import queue
import threading
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
exitFlag = 0
q = queue.Queue()
chrome_options = Options()
chrome_options.add_argument('--headless')
class scrapy_biquge():
def get_url(self):
browser = webdriver.Chrome(chrome_options=chrome_options)
browser.get('http://www.xbiquge.la/xuanhuanxiaoshuo/')
content = browser.find_element_by_class_name("r")
content = content.find_elements_by_xpath('//ul/li/span[@class="s2"]/a')
for i in content:
title = i.text
href = i.get_attribute('href')
print(title+'+'+href)
q.put(title+'+'+href)
browser.close()
browser.quit()
class myThread (threading.Thread): # 繼承父類threading.Thread
def __init__(self, threadID, name, counter):
threading.Thread.__init__(self)
self.threadID = threadID
self.name = name
self.counter = counter
def run(self): #把要執行的代碼寫到run函數裡面 線程在建立後會直接運作run函數
print(self.name)
while not exitFlag:
queueLock.acquire()
if not q.empty():
item = q.get()
queueLock.release()
title = item.split('+')[0]
href = item.split('+')[1]
get_content(title, href)
else:
print('資料全部結束')
queueLock.release()
def get_content(title, href):
browser = webdriver.Chrome(chrome_options=chrome_options)
browser.get(href)
browser.find_element_by_id('list')
novel_content = browser.find_elements_by_xpath('//dl/dd/a')
for novel in novel_content:
novel_dir = novel.text
novel_dir_href = novel.get_attribute('href')
print(title, novel_dir, novel_dir_href)
browser.close()
browser.quit()
if __name__ == '__main__':
# 所有url進隊列以後,啟動線程
scrapy_biquge().get_url()
threadList = ["Thread-1", "Thread-2", "Thread-3"]
queueLock = threading.Lock()
threads = []
threadID = 1
# 建立新線程
for tName in threadList:
thread = myThread(threadID, tName, q)
thread.start()
threads.append(thread)
threadID += 1
# 等待隊列清空
while not q.empty():
pass
# 通知線程是時候退出
exitFlag = 1
# 等待所有線程完成
for t in threads:
t.join()
print("Exiting Main Thread")
上面的例子用了FIFO隊列。當然你也可以換成其他類型的隊列.
LifoQueue
後進先出
Priority Queue
優先隊列
Priority Queue
Python多程序,multiprocessing,下次使用多程序跑這個代碼。
參考: https://cuiqingcai.com/3325.html