Python 之 threading(多執行緒)用法教程
Python 版本是3.7.4
前面的文章記錄了網路請求(urllib,requests)、資料提取(beautiful,xpath,正則)、資料儲存(json,csv)的學習,下面進行一個多執行緒的學習。
多執行緒爬蟲
有些時候,比如下載圖片,因為下載圖片是一個耗時的操作,如果採用之前那種同步的方式下載,那效率會特別慢。這時候我們就可以考慮使用多執行緒的方式來下載圖片。
多執行緒介紹
多執行緒是為了同步完成多項任務,透過提高資源使用來提高系統的效率,執行緒是在同一時間需要完成多項任務的時候是西納的,最簡單的比喻多執行緒就像火車的每一節車廂,二程式就是火車。車廂離開火車是無法跑動的,同理火車可以有多節車廂,多執行緒的出現是為了提高效率,同時他的出現也帶來一些問題。
簡單來講,多執行緒就相當於你原來開了一個視窗爬取,限制開了十個視窗來爬取。
threading模組介紹
threading模組是Python中專門提供用來做多執行緒的模組。threading模組中最常用的類是Thread。下面一個簡單的多執行緒程式:
# 引入所需庫
import threading
import time
def coding():
"""
coding函式
:return:
"""
for x in range(5):
print('%s 號程式設計師正則寫程式碼...' % x)
time.sleep(1)
def drawing():
"""
drawing函式
:return:
"""
for x in range(5):
print('%s 號設計師正在設計圖片...' % x)
time.sleep(1)
def single_thread():
"""
單執行緒執行
:return:
"""
coding()
drawing()
def multi_thread():
"""
多執行緒執行
:return:
"""
# 建立執行緒
# 注意:target引數是函式名,不能帶括號
t1 = threading.Thread(target=coding, name='coding')
t2 = threading.Thread(target=drawing, name='drawing')
# 啟動執行緒
t1.start()
t2.start()
if __name__ == '__main__':
# single_thread()
multi_thread()
檢視執行緒數:
num = threading.enumerate()
print(num)
檢視當前程式名字:
threading.current_thread()
Thread類的使用
為了讓執行緒程式碼更好的封裝,可以使用threading模組下的Thread類,繼承自這個類,然後實現run()方法,執行緒就會自動執行run()方法中的程式碼,示例程式碼如下:
# 引入所需庫
import threading
import time
class CodingThread(threading.Thread):
"""
寫程式程式類
"""
def run(self):
for x in range(5):
print('%s 號程式設計師正則寫程式碼...' % threading.current_thread())
time.sleep(1)
class DrawingThread(threading.Thread):
"""
設計程式類
"""
def run(self):
for x in range(5):
print('%s 號設計師正在設計圖片...' % threading.current_thread())
time.sleep(1)
def multi_thread():
t1 = CodingThread()
t2 = DrawingThread()
t1.start()
t2.start()
if __name__ == '__main__':
multi_thread()
多執行緒共享全域性變數問題
多執行緒都是在同一個程式中執行的,因此在程式中的全域性變數所有的執行緒都是可以共享的。這就造就了一個問題,因為執行緒執行的順序是無序的,有可能會造成資料錯誤。例如如下程式碼:
# 引入threading庫
import threading
# 定義全域性變數
VALUE = 0
def add_value():
"""
增加數值
:return:
"""
global VALUE
for x in range(1000000):
VALUE += 1
print(VALUE)
def main():
for x in range(2):
t = threading.Thread(target=add_value)
t.start()
if __name__ == '__main__':
main()
以上的程式碼結果正常來講應該是:
1000000
2000000
但是由於多執行緒執行的不確定性,因此結果可能是隨機的。
鎖機制
為了解決上述問題由於多執行緒執行的不確定性,threading庫增加了Lock類鎖機制進行處理,當某個執行緒對全域性變數進行修改時則將此變數加鎖不允許其他執行緒進行修改,知道當前執行緒修改完這個變數之後再進行解鎖釋放,之後其他執行緒才可進行修改,這就保證了資料的安全性。修改上述程式碼如下:
# 引入threading庫
import threading
# 定義全域性變數
VALUE = 0
# 建立鎖
gLock = threading.Lock()
def add_value():
"""
增加數值
:return:
"""
global VALUE
# 加鎖
gLock.acquire()
for x in range(1000000):
VALUE += 1
# 解鎖
gLock.release()
print(VALUE)
def main():
for x in range(2):
t = threading.Thread(target=add_value)
t.start()
if __name__ == '__main__':
main()
Lock版生產者和消費者模式
生產者和消費者模式時多執行緒開發中的經常見到的一種模式。生產者的執行緒專門用來生產一些資料,然後存放到一箇中間的變數中。消費者再從這個中間的變數中取出資料進行消費,但是因為要使用中間變數,中間變數經常是一些全域性變數,因此需要使用鎖來保證資料的完整性。以下是使用threading.Lock()鎖實現“生產者與消費者模式”的一個例子:
# 引入所需庫
import random
import threading
import time
gMoney = 1000
gTimes = 0
# 定義鎖
gLock = threading.Lock()
class Producer(threading.Thread):
"""
生產者
"""
def run(self):
global gMoney
global gTimes
while True:
money = random.randint(100, 1000)
gLock.acquire()
# 僅允許生產10次
if gTimes >= 10:
gLock.release()
break
gMoney += money
print('%s生產了%d元錢,剩餘%d元錢' % (threading.current_thread(), money, gMoney))
gTimes += 1
gLock.release()
time.sleep(0.5)
class Consumer(threading.Thread):
"""
消費者
"""
def run(self):
global gMoney
while True:
money = random.randint(100, 1000)
gLock.acquire()
if gMoney >= money:
gMoney -= money
print('%s消費了%d元錢,剩餘%d元錢' % (threading.current_thread(), money, gMoney))
else:
if gTimes >= 10:
gLock.release()
break
print("%s消費者消費錢不夠,不消費" % threading.current_thread())
gLock.release()
time.sleep(0.5)
def main():
# 定義三個消費者
for x in range(3):
t = Consumer(name='消費者執行緒%d' % x)
t.start()
# 定義五個生產者
for x in range(5):
t = Producer(name='生產者執行緒%d' % x)
t.start()
if __name__ == '__main__':
main()
Condition版生產者與消費者模式
Lock()版的生產者與消費者模式可以正常的執行,但是存在一個不足,在消費者中,總是透過while True死迴圈並且上鎖的方法去判斷錢夠不夠,上鎖是一個很好CPU資源的行為。因此這種方式不是最好的,還有一種更好的方式便是使用threading.Condition來實現。
threading.Condition可以在沒有資料的時候處於阻塞等等狀態。一旦有合適的資料了,還可以使用notify相關的函式來通知其他處於等待狀態的執行緒,這樣就可以不用做一些無用的上鎖和解鎖的操作,可以提高程式的效能。
首先對threading.Condition相關的函式做個介紹,threading.Condition類似threading.Lock,可以在修改全部資料的時候進行上鎖,也可以在修改完畢後進行解鎖。以下將一些常用的函式做個簡單的介紹:
acquire : 上鎖
release : 解鎖
wait : 將當前執行緒處於等待狀態,並且會釋放鎖。可以被其他執行緒使用notify和notify_all函式喚醒,被喚醒後會繼續等待上鎖,上鎖後繼續執行後續的程式碼
notify : 通知某個正在等待的執行緒,預設是第1個等待的執行緒
notify_all : 通知所有正在等待的執行緒。notify和notify_all不會釋放鎖,並且需要在release之前掉用
Condition版生產者與消費者模式示例程式碼如下:
# 引入所需庫
import random
import threading
import time
gMoney = 1000
gTimes = 0
# 定義Condition
gCondition = threading.Condition()
class Producer(threading.Thread):
"""
生產者
"""
def run(self):
global gMoney
global gTimes
while True:
money = random.randint(100, 1000)
gCondition.acquire()
# 僅允許生產10次
if gTimes >= 10:
gCondition.release()
break
gMoney += money
print('%s生產了%d元錢,剩餘%d元錢' % (threading.current_thread(), money, gMoney))
gTimes += 1
gCondition.notify_all()
gCondition.release()
time.sleep(0.5)
class Consumer(threading.Thread):
"""
消費者
"""
def run(self):
global gMoney
while True:
money = random.randint(100, 1000)
gCondition.acquire()
while gMoney < money:
if gTimes >= 10:
gCondition.release()
return
print("%s消費者消費錢不夠,不消費" % threading.current_thread())
gCondition.wait()
gMoney -= money
print('%s消費了%d元錢,剩餘%d元錢' % (threading.current_thread(), money, gMoney))
gCondition.release()
time.sleep(0.5)
def main():
# 定義三個消費者
for x in range(3):
t = Consumer(name='消費者執行緒%d' % x)
t.start()
# 定義五個生產者
for x in range(5):
t = Producer(name='生產者執行緒%d' % x)
t.start()
if __name__ == '__main__':
main()
Queue執行緒安全佇列
線上程中,訪問一些全域性變數,加鎖是一個經常的過程,如果你想把一些資料儲存到莫格佇列中,那麼Python內建了一個執行緒安全的模組叫queue模組。Python中的queue模組中提供了同步的、執行緒安全的對咧咧,包括FIFO(先進先出)佇列Queue,LIFO(後入先出)佇列LifoQueue。這些佇列都實現了所原理(可以理解為原子操作,即要麼不做,要麼都做完),能夠在多執行緒中直接使用。可以使用佇列來實現執行緒間的同步,相關函式如下:
初始化Queue(maxsize) : 建立一個先進先出的佇列
qsize() : 返回佇列的大小
empty() : 判斷佇列是否為空
full() : 判斷佇列是否已滿
get() : 從佇列中獲取最後一個資料
put() : 將一個資料放到佇列中
使用程式碼示例:
# 引入所需庫
import threading
import time
from queue import Queue
def set_value(q):
"""
寫入佇列
:param q:
:return:
"""
index = 1
while True:
q.put(index)
index += 1
time.sleep(3)
def get_value(q):
"""
從佇列取值
:param q:
:return:
"""
while True:
print(q.get())
# time.sleep(4)
# print("qsize:", q.qsize())
def main():
"""
主函式
:return:
"""
q = Queue(5)
t1 = threading.Thread(target=set_value, args=[q])
t2 = threading.Thread(target=get_value, args=[q])
t1.start()
t2.start()
if __name__ == '__main__':
main()
使用例項
單執行緒爬取表情包,例項程式碼如下:
# 引入所需庫
import os
import re
import requests
from lxml import etree
def parse_page(url):
"""
請求 解析 下載
:param url:
:return:
"""
# 宣告定義請求頭
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
} 無錫人流醫院哪家好
req = requests.get(url=url, headers=headers)
html = req.text
tree = etree.HTML(html)
imgs = tree.xpath('//div[@class="page-content text-center"]//img[@class!="gif"]')
for img in imgs:
img_url = img.get('data-original')
alt = img.get('alt')
alt = re.sub(r'[\??\..,!!]]', '', alt)
suffix = os.path.splitext(img_url)[1]
file_name = alt + suffix
req_img = requests.get(url=img_url, headers=headers)
with open('images/' + file_name, 'wb') as fp:
fp.write(req_img.content)
print(file_name)
def main():
"""
主函式
:return:
"""
for x in range(1, 101):
print("第%d頁開始下載..." % x)
url = '%d' % x
parse_page(url)
print("第%d頁結束下載..." % x)
if __name__ == '__main__':
main()
多執行緒爬取表情包,例項程式碼如下:
# 引入所需庫
import os
import re
import threading
from queue import Queue
import requests
from lxml import etree
class Producer(threading.Thread):
"""
生產者 - 手機表情包圖片地址
"""
def __init__(self, page_queue, img_queue):
super(Producer, self).__init__()
self.page_queue = page_queue
self.img_queue = img_queue
def run(self):
while True:
if self.page_queue.empty():
break
url = self.page_queue.get()
self.parse_page(url)
def parse_page(self, url):
"""
請求 解析 下載
:param url:
:return:
"""
# 宣告定義請求頭
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
}
req = requests.get(url=url, headers=headers)
html = req.text
tree = etree.HTML(html)
imgs = tree.xpath('//div[@class="page-content text-center"]//img[@class!="gif"]')
for img in imgs:
img_url = img.get('data-original')
alt = img.get('alt')
alt = re.sub(r'[\??\..,!!\*]]', '', alt)
suffix = os.path.splitext(img_url)[1]
file_name = alt + suffix
self.img_queue.put((img_url, file_name))
class Consumer(threading.Thread):
"""
消費者 - 下載表情包圖片
"""
def __init__(self, page_queue, img_queue):
super(Consumer, self).__init__()
self.page_queue = page_queue
self.img_queue = img_queue
def run(self):
# 宣告定義請求頭
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
}
while True:
if self.img_queue.empty() and self.page_queue.empty():
break
img_url, file_name = self.img_queue.get()
req_img = requests.get(url=img_url, headers=headers)
with open('images/' + file_name, 'wb') as fp:
fp.write(req_img.content)
print(file_name + '下載完成...')
def main():
"""
主函式
:return:
"""
page_queue = Queue(100)
img_queue = Queue(1000)
for x in range(1, 101):
url = '%d' % x
page_queue.put(url)
# 定義五個生產者
for x in range(6):
t = Producer(page_queue=page_queue, img_queue=img_queue)
t.start()
# 定義三個消費者
for x in range(4):
t = Consumer(page_queue=page_queue, img_queue=img_queue)
t.start()
if __name__ == '__main__':
main()
GIL全域性直譯器鎖
Python自帶的直譯器是CPython。CPython直譯器的多執行緒實際上是一個家的多執行緒(在多核CPU中,只能利用一核,不能利用多核)。同一時刻只有一個執行緒在執行,為了保證同一時刻只有一個執行緒在執行,在CPython直譯器中有一個功能叫做GIL,叫做全域性直譯器鎖。這個直譯器鎖是有必要的,因為CPython直譯器的記憶體管理不是執行緒安全的,當然除了CPython直譯器,還有其他的直譯器,有些直譯器是沒有GIL鎖的,見下面:
Jpython : 用Java實現的Python直譯器,不存在GIL鎖。更多詳情請見:
IronPython : 用.NET實現的Python直譯器,不存在GIL鎖。更多詳情請見:
PyPy : 用Python實現的Python直譯器,存在GIL鎖。更多詳情請見:
GIL雖然是一個假的多執行緒,但是在處理IO操作
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69945560/viewspace-2659876/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- python之 threading(多執行緒)模組Pythonthread執行緒
- 【莫煩】Threading 多執行緒教程thread執行緒
- Python多執行緒之_thread與threading模組Python執行緒thread
- python中的socket+threading多執行緒Pythonthread執行緒
- Python 多執行緒 threading和multiprocessing模組Python執行緒thread
- Python 快速教程(標準庫11):多執行緒與同步 (threading包)Python執行緒thread
- Python 快速教程(標準庫08):多執行緒與同步 (threading包)Python執行緒thread
- threading多執行緒資源加鎖thread執行緒
- threading 多執行緒控制和處理thread執行緒
- 多執行緒之 NSOperation 基礎用法執行緒
- python threading 執行緒原理實驗Pythonthread執行緒
- Python模組學習:threading 多執行緒控制和處理Pythonthread執行緒
- java 多執行緒 CountDownLatch用法Java執行緒CountDownLatch
- Python3 多執行緒程式設計(thread、threading模組)Python執行緒程式設計thread
- python 多執行緒之threadPython執行緒thread
- 多執行緒-執行緒控制之休眠執行緒執行緒
- 多執行緒-執行緒控制之加入執行緒執行緒
- 多執行緒-執行緒控制之禮讓執行緒執行緒
- Java多執行緒之執行緒中止Java執行緒
- 多執行緒系列之 執行緒安全執行緒
- iOS 多執行緒之執行緒安全iOS執行緒
- iOS多執行緒之執行緒安全iOS執行緒
- 多執行緒-執行緒控制之守護執行緒執行緒
- 多執行緒的Python 教程--“貪吃蛇”執行緒Python
- uwsgi+django+gmail+threading多執行緒發郵件DjangoAIthread執行緒
- python多執行緒Python執行緒
- Python 多執行緒Python執行緒
- Android多執行緒之執行緒池Android執行緒
- java多執行緒系列之執行緒池Java執行緒
- 多執行緒之Runloop執行緒OOP
- Python 多執行緒多程式Python執行緒
- 多執行緒程式設計ExecutorService用法執行緒程式設計
- java多執行緒之執行緒的基本使用Java執行緒
- java--多執行緒之後臺執行緒Java執行緒
- python多執行緒示例Python執行緒
- python多執行緒中:如何關閉執行緒?Python執行緒
- Python——程式、執行緒、協程、多程式、多執行緒(個人向)Python執行緒
- 【Python】 多程式與多執行緒Python執行緒