Python多執行緒與多程序程式設計(一) 就這麼簡單
"""
<axiner>宣告:
(錯了另刂扌丁我)
(如若有誤,請記得指出喲,謝謝了!!!)
"""
先來了解一個概念,GIL? GIL的全稱為Global Interpreter Lock, 全域性直譯器鎖。
Python程式碼的執行由Python 虛擬機器(也叫直譯器主迴圈,CPython版本)來控制,Python 在設計之初就考慮到要在直譯器的主迴圈中,同時只有一個執行緒在執行,即在任意時刻,只有一個執行緒在直譯器中執行。對Python 虛擬機器的訪問由全域性直譯器鎖(GIL)來控制,正是這個鎖能保證同一時刻只有一個執行緒在執行。
也就是說並沒有正真的多執行緒.....
為什麼又有多執行緒程式設計呢?
GIL鎖的釋放,並不是GIL鎖的獲取者會一條路走到黑,也就是說在執行的某些中途會釋放GIL鎖,此時其它就有機會獲得GIL鎖了.....
Python內部演算法機制有幾種釋放GIL鎖的方式:
1\ 時間片
2\ 位元組碼長度
3\ io操作時
多執行緒的實現方式:
from threading import Thread
1、引數傳入
thread = Thread()
thread(target=func, args=())
2、類的繼承
class MyThread(Thread):
def __init__(self):
pass
def run(self):
# 重寫類下run()方法
pass
thread = Thread()
thread.SetDeamon(True) # 設定為保護執行緒(即主執行緒退出其也退出)
thread.join() # 將執行緒阻塞直至執行緒完成
---------
執行緒間通訊:
1、全域性共享變數。資源競爭,不安全
2、queue。安全(from queue import Queue)
注意:
queue.join() # 阻塞作用。要退出,則在其前加上queue.task_done()<成對出現>
---------
執行緒間同步:
1、Lock, RLook
使用:在需要的程式碼塊加上鎖,acquire獲取鎖-release釋放鎖
鎖的缺點:
1、鎖會影響效能;
2、鎖會引起死鎖。
引起死鎖原因:a.多次acquire而不釋放; b.互相等待對方造成資源競爭; c.lock中的子函式也有lock(此應用RLock)
2、Condition(條件變數)
from threading import Condition
例如:A與B對話(A先說)
from threading import Thread, Condition
class A(Thread):
def __init__(self, cond):
super().__init__(name="A")
self.conf = cond
def __run__(self):
with self.cond: # 獲取condition
print("{}: hello!".format(self.name))
self.cond.notify() # 通知等待(wait)
self.cond.wait() # 阻塞等待通知(notify)
class B(Thread):
def __init__(self, cond):
super().__init__(name="B")
self.conf = cond
def __run__(self):
with self.cond: # 獲取condition
self.cond.wait()
print("{}: 你好!".format(self.name))
self.cond.notify()
self.cond.wait()
3、Semaphore(訊號)
# 是用於控制進入數量的鎖(檔案的讀寫,寫一般只用一個執行緒,讀可以允許有多個執行緒)
例如:爬取url及解析頁面
from threading import Thread, Semaphore
class Html_Spider(Thread):
def __init__(self, sem):
super().__init__(name="B")
self.sem = sem
def __run__(self):
time.sleep(2) # 模擬
print("success...")
self.sem.release() # 完成後釋放
class UrlProducter(Thread):
def __init__(self, sem):
super().__init__(name="B")
self.sem = sem
def __run__(self):
for i in range(20):
self.acquire() # 獲取
html_thread = Html_Spider("http://www.foo/{0}".format(i), self.sem)
html_thread.start()
if __name__ == "__main__":
sem = Semaphore(3) # 鎖的數量
url_producter = UrlProducter(sem)
url_producter.start()
---------
關於執行緒池
為什麼用執行緒池?
1、控制執行緒數量併發(semaphore也有這功能)
2、可執行緒狀態及返回值
3、當一個執行緒完成時,主執行緒可以立即知道
(4、futures可以讓多執行緒和多程序介面一致)
from concurrent import futures
eg:
# 模擬html獲取
import time
from concurrent.futures import ThreadPoolExecutor
def get_html(t):
time.sleep(2)
print("get page success: {0}".format(t))
return t
executor = ThreadPoolExecutor(max_work=2)
1\\ 提交任務
# 1-窮舉提交
task1 = executor.submit(get_html, (2,))
task2 = executor.submit(get_html, (4,))
# 2-批量提交
ts = [2, 4]
all_task = [executor.submit(get_html, t) for t in ts]
# 總結
executor.submit() # 將執行函式提交到執行緒池中,非阻塞立即返回
另:
executor.done() # 判斷某任務是否完成
executor.result() # 獲取結果,阻塞式
executor.cancel() # 未提交的任務可取消
from concurrent.futures import wait
wait() # 等待xx完成,才執行主執行緒
wait有timeout和return_when兩個引數可以設定。
timeout控制wait()方法返回前等待的時間。
return_when決定方法什麼時間點返回:如果採用預設的ALL_COMPLETED,程式會阻塞直到執行緒池裡面的所有任務都完成;如果採用FIRST_COMPLETED引數,程式並不會等到執行緒池裡面所有的任務都完成。
2\\ 獲取結果
from concurrent.futures import as_completed
# 一
for future in as_completed(all_task):
data = future.result()
print("get success page: {0}".format(data))
------------
另:提交任務+獲取結果
for data in executor.map(get_html, ts):
print(data)
# as_completed 與 executor.map
as_completed是concurrent.futures的函式,返回futures物件(順序與執行相同)
map是futures.ThreadPoolExecutor下的方法,返回data結果(順序與提交相同)
多程序程式設計>>>見下篇