# -*- coding: utf-8 -*-
'''
Py併發編程及應用.py
(多線程:創建、同步鎖、線程通信、線程池、線程局部變量、線程定時器、任務調度器)
(多進程:創建、進程池、進程通信、併發及異步併發爬蟲、全局解釋器鎖、垃圾回收機制)
深入:
1、線程鎖、線程通信的概念和運用
2、任務調度器的運用
3、asyncio模塊 異步併發模塊(爬蟲應用)
4、aiohttp模塊 異步的requests(爬蟲應用)
注意:
1、併發和並行是兩個概念,
並行指在同一時刻有多條指令在多個處理器上同時執行;
併發是指在同一時刻只能有一條指令執行,但多個進程指令被快速輪換執行,使得在宏觀上具有多個進程同時執行的效果。
2、默認情況下,主線程的名字爲 MainThread,用戶啓動的多個線程的名字依次爲 Thread-1、Thread-2、Thread-3、...、Thread-n 等。
3、注意線程執行完成後已經死亡狀態,再次執行將引發 RuntimeError異常。另外對處於新建狀態的線程兩次調用start()方法也會引發異常。
4、如果要將某個線程設置爲後臺線程,則必須在該線程啓動之前進行設置。也就是說,將 daemon 屬性設爲 True,必須在 start() 方法調用之前進行,否則會引發 RuntimeError 異常。
5、加鎖操作RLock較爲常用,注意加鎖方式一般爲 先加鎖,然後try操作,最後finally解鎖。
6、注意解決死鎖問題的方式有:1、避免多次加鎖 2、相同的加鎖順序 3、使用定時鎖 即調用acquire()方法加鎖時候指定timeout參數 4、死鎖檢測
7、注意線程池返回的Future對象方法result()會阻塞當前主線程;另外線程池類方法map()後可循環迭代每一個線程
8、Timer定時器只能控制函數在指定時間內執行一次。
如果要使用 Timer 控制函數多次重複執行,則需要函數內嵌套定時器 來循環執行下一次調度。
9、創建子進程時,multiprocessing.Process類通過實例化對象指定target參數執行函數 在IDLE裏不顯示函數執行內容。
通過在控制檯通過執行文件可顯示target參數執行函數的內容。
10、通過 multiprocessing.Process 來創建並啓動進程時,程序必須先判斷if __name__=='__main__':,否則可能引發異常。
11、進程通信中,使用實例化創建multiprocessing.Pipe類管道對象返回兩個PipeConnection對象,
其中前一個PipeConnection對象用於接受數據,後一個PipeConnection對象用於發送數據
12、GIL 不能絕對保證線程安全
因爲即便 GIL 僅允許一個 Python 線程執行,但Python 還有 check interval 這樣的搶佔機制。
使用:
第一部分:多線程相關
threading模塊 線程封裝
一、Python 主要通過兩種方式來創建線程:
1、使用 threading 模塊中的 Thread 類的構造器創建線程。
即直接對類 threading.Thread 進行實例化,並調用實例化對象的 start 方法創建線程。
2、繼承 threading 模塊中的 Thread 類創建線程類。
即用 threading.Thread 派生出一個新的子類,將新建類實例化,並調用其 start 方法創建線程。
二、Lock類 和 Rlock類 互斥鎖 用來解決數據不同步的問題
threading模塊提供了 Lock 和 RLock 兩個類,都各自提供兩個方法來 加互斥鎖 和 解除互斥鎖
三、線程通信
當線程在系統中運行時,線程的調度具有一定的透明性,通常程序無法準確控制線程的輪換執行,如果有需要,Python 可通過線程通信來保證線程協調運行。
線程通信的實現方式:
1、Condition類 來自threading模塊
2、queue模塊 隊列
3、Event類 來自threading模塊
四、線程池 ThreadPoolExecutor類
Executor 提供了兩個子類,即 ThreadPoolExecutor 和 ProcessPoolExecutor,
其中 ThreadPoolExecutor類 用於創建線程池,而 ProcessPoolExecutor類 用於創建進程池。
1、線程池類方法(submit()、map()、shudown())
2、submit()類方法提交線程池後返回的Future對象方法
Future對象用result()方法來獲取線程任務的運回值,但該方法會阻塞當前主線程,只有等到錢程任務完成後,result() 方法的阻塞纔會被解除。
可通過 Future 的 add_done_callback() 方法來添加回調函數來避免直接調用result()方法阻塞線程
另外線程池類方法map()後可循環迭代每一個線程
五、線程局部變量 threading.local()
六、定時器 Timer子類 來自threading模塊
七、任務調度器 sched模塊 任務調度器模塊
1、實例化任務調度器 sched.scheduler()
2、實例化任務調度器方法:絕對時間執行函數任務、相對時間執行函數任務、取消任務等
第二部分:多進程相關
multiprocessing模塊 進程封裝
八、multiprocessing.Process類 進程黃建
Python 在 multiprocessing模塊下提供了 Process類 來創建新進程。
與 Thread 類似的是,使用 Process 創建新進程也有兩種方式:
1、使用 multiprocessing模塊 中的 Process類 來實例化對象創建新進程,並指定函數作爲target參數函數
2、繼承 Process類 ,並重寫他的 run() 方法來創建進程類,程序創建Process子類的實例作爲進程
九、進程啓動的三種方式 spawn(windows僅支持此方式)、fork、forkserver
multiprocessing.set_start_method(' ')函數用於設置啓動進程的方式,設置代碼必須放在多進程代碼之前。
十、進程池 multiprocessing.Pool類
通過 multiprocessing 模塊的 Pool類來實例化創建進程池,然後通過進程池方法提交進程池、關閉進程池、等待進程池等
十一、進程通信 multiprocessing.Queue類 和 multiprocessing.Pipe類
Python 爲進程通信提供了兩種機制:
Queue:一個進程向 Queue 中放入數據,另一個進程從 Queue 中讀取數據。
Pipe:Pipe 代表連接兩個進程的管道。程序在調用 Pipe() 函數時會產生兩個連接端,分別交給通信的兩個進程,接下來進程既可從該連接端讀取數據,也可向該連接端寫入數據。
十二、異步併發模塊的性能提升需要後期單獨重點深入
asyncio模塊 異步併發模塊
aiohttp模塊 異步的requests
十三、垃圾回收機制 垃圾回收機制引用計數機制
引用計數機制 即:對象的引用計數值爲 0 時,說明這個對象永不再用,自然它就變成了垃圾,需要被回收。
gc模塊 手動啓動垃圾回收機制
'''
# =============================================================================
# #threading模塊 線程封裝
# #threading模塊 Python 3 之後的線程模塊,提供了功能豐富的多線程支持
# =============================================================================
#Python 主要通過兩種方式來創建線程:
#1、使用 threading 模塊中的 Thread 類的構造器創建線程。
#即直接對類 threading.Thread 進行實例化,並調用實例化對象的 start 方法創建線程。
#2、繼承 threading 模塊中的 Thread 類創建線程類。
#即用 threading.Thread 派生出一個新的子類,將新建類實例化,並調用其 start 方法創建線程。
import threading
help(threading)
threading.__doc__
threading.__file__
threading.__all__
dir(threading)
#使用 threading 模塊中的 Thread 類的構造器創建線程。
#__init__(self, group=None, target=None, name=None, args=(), kwargs=None, *,daemon=None)
#構造器涉及如下幾個參數:
#group: 指定該線程所屬的線程組。目前該參數還未實現,因此它只能設爲 None。
#target: 指定該線程要調度的目標方法。
#args: 指定一個元組,以位置參數的形式爲 target 指定的函數傳入參數。元組的第一個元素傳給 target 函數的第一個參數,元組的第二個元素傳給 target 函數的第二個參數……依此類推。
#kwargs: 指定一個字典,以關鍵字參數的形式爲 target 指定的函數傳入參數。
#daemon: 指定所構建的線程是否爲後臺線程 即 守護線程。
help(threading.Thread)
threading.Thread.__doc__
dir(threading.Thread)
help(threading.Thread.start)
help(threading.Thread.run)
help(threading.Thread.daemon)
help(threading.Thread.join)
####################
#創建線程方法一: 通過Thread類的構造器創建並啓動多線程的步驟
#1、調用 Thread 類的構造器創建線程對象。在創建線程對象時,target參數 指定的函數將作爲線程執行體。
#2、調用線程對象的 start() 方法啓動該線程。
import threading
#先定義一個普通的action函數,準備作爲線程執行體 即:target參數函數
def action(max):
for i in range(max):
#threading.current_thread()獲取當前線程
#.getName()獲取名稱
print(threading.current_thread().getName() + ' ' + str(i)) #current_thread()獲取當前線程;#getName()獲取當前線程的名字
#主線程的執行體
for i in range(100):
#輸出當前線程的名稱
print(threading.current_thread().getName() + ' ' + str(i))
if i == 20:
#創建並啓動第一個線程
#target參數 指定函數作爲線程執行體,
#args參數 元組形式位置參數,用於指定爲target參數的函數的參數。
t1=threading.Thread(target = action, args=(100,))
t1.start()
#創建並啓動第二個線程
t2=threading.Thread(target = action, args=(100,))
t2.start()
#注意:
#默認情況下,主線程的名字爲 MainThread,用戶啓動的多個線程的名字依次爲 Thread-1、Thread-2、Thread-3、...、Thread-n 等。
print('主線程執行完成!')
#Threading 模塊中,除了 current_thread() 函數外,還經常使用如下 2 個函數:
#threading.enumerate(): 返回一個正運行線程的 list。“正運行”是指線程處於“啓動後,且在結束前”狀態,不包括“啓動前”和“終止後”狀態。
#threading.activeCount(): 返回正在運行的線程數量。與 len(threading.enumerate()) 有相同的結果
####################
#創建線程方法二: 通過繼承 Thread 類來創建並啓動線程的步驟
#1、定義 Thread 類的子類,並重寫該類的 run() 方法。
#run() 方法的方法體就代表了線程需要完成的任務,因此把 run() 方法稱爲線程執行體。
#2、創建 Thread 子類的實例,即創建線程對象。
#3、調用線程對象的 start() 方法來啓動線程。
import threading
#先定義類繼承Thread類,來準備實例化創建和啓動線程
class FkThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
self.i = 0
#重寫run()方法,run()方法代表線程需要完成的任務,成爲線程執行體
def run(self):
while self.i < 100:
print(threading.current_thread().getName() + ' ' + str(self.i))
self.i += 1
for i in range(100):
print(threading.current_thread().getName() + ' ' + str(i))
if i == 20:
#實例化來創建第一個線程 然後 啓動線程
ft1 = FkThread()
ft1.start()
#實例化來創建第二條線程 然後 啓動線程
ft2 = FkThread()
ft2.start()
print('主線程執行完成!')
####################
#線程的狀態
##########
#線程的新建狀態 和 線程的就緒狀態
#啓動線程使用start()方法,而不是run()方法
#調用 start() 方法來啓動線程,系統會把該 run() 方法當成線程執行體來處理;
#在調用線程對象的 start() 方法之後,該線程立即進入就緒狀態(相當於“等待執行”),但該線程並未真正進入運行狀態。
#但如果直接調用線程對象的 run() 方法,則 run() 方法立即就會被執行,而且在該方法返回之前其他線程無法併發執行。
import threading
# 先定義函數 準備作爲線程執行體的target參數函數
def action(max):
for i in range(max):
#直接調用run()方法時,Thread的name屬性返回的是該對象的名字
#而不是當前線程的名字
#使用threading.current_thread().name總是獲取當前線程的名字
print(threading.current_thread().name + " " + str(i))
for i in range(100):
#調用Thread的currentThread()方法獲取當前線程
print(threading.current_thread().name + " " + str(i))
if i == 20:
# 直接調用線程對象的run()方法
# 系統會把線程對象當成普通對象,把run()方法當成普通方法
# 所以下面兩行代碼並不會啓動兩個線程,而是依次執行兩個run()方法
threading.Thread(target=action,args=(100,)).run()
threading.Thread(target=action,args=(100,)).run()
##########
#線程的運行狀態
#如果處於就緒狀態的線程獲得了 CPU,開始執行 run() 方法的線程執行體,則該線程處於運行狀態。
##########
#線程的阻塞狀態
#線程將會進入阻塞狀態的幾種情況:
#1、線程調用 sleep() 方法主動放棄其所佔用的處理器資源。
#2、線程調用了一個阻塞式 I/O 方法,在該方法返回之前,該線程被阻塞。
#3、線程試圖獲得一個鎖對象,但該鎖對象正被其他線程所持有。關於鎖對象的知識,後面將有更深入的介紹。
#4、線程在等待某個通知(Notify)。
##########
#線程的死亡狀態
#線程結束後就處於死亡狀態。線程結束有兩種方式:一種是run()方法或代表線程執行體的targe函數執行完成。另一種是出現異常。
#is_alive()方法 測試某個顯示是否已經死亡。
#當線程處於就緒、運行、阻塞三種狀態時,該方法將返回 True;當線程處於新建、死亡兩種狀態時,該方法將返回 False。
import threading
#先定義一個函數 用於準備作爲線程執行體target參數函數來使用
def action(max):
'''
先定義一個函數 用於準備作爲線程執行體target參數函數來使用
循環輸出當前線程名稱100次
'''
for i in range(max):
print(threading.current_thread().name + ' ' + str(i))
#創建線程對象 進入新建狀態
sd=threading.Thread(target= action, args=(100,))
for i in range(300):
print(threading.current_thread().name + ' ' + str(i))
if i == 20:
#啓動線程 進入就緒狀態
sd.start()
print(sd.is_alive()) #當線程處於就緒、運行、阻塞三種狀態時,該方法將返回 True;當線程處於新建、死亡兩種狀態時,該方法將返回 False。
# if i > 20:
# #注意線程執行完成後已經死亡狀態,再次執行將引發 RuntimeError異常。另外對處於新建狀態的線程兩次調用start()方法也會引發異常。
# sd.start()
####################
#join()方法
#join()方法讓一個線程等待另一個線程完成。
#當某個程序執行流中調用其他線程的 join() 方法時候,調用線程將被阻塞,直到被join()方法加入的join線程執行完成。
import threading
#先定義一個函數 用於準備作爲線程執行體target參數函數來使用
def action(max):
'''
先定義一個函數 用於準備作爲線程執行體target參數函數來使用
循環輸出當前線程名稱100次
'''
for i in range(max):
print(threading.current_thread().name + ' ' + str(i))
#直接新建並啓動 子線程
threading.Thread(target=action,args=(100,),name='新線程').start()
#演示join()方法
for i in range(100):
if i == 20:
#演示阻塞第20次執行線程,等運行完 被join的線程後 繼續運行20次以後的線程
jt=threading.Thread(target=action,args=(100,),name='被join的線程')
jt.start()
#join(timeout=None)方法 還可以指定一個 timeout 參數,該參數指定等待被 join 的線程的時間最長爲 timeout 秒。如果在 timeout 秒內被 join 的線程還沒有執行結束,則不再等待
jt.join()
print(threading.current_thread().name + ' ' + str(i))
####################
#後臺線程(Daemon Thread)”,又稱爲“守護線程”或“精靈線程。調用 Thread 對象的 daemon 屬性可以將指定線程設置成後臺線程
#創建後臺線程有兩種方式:
#1、主動將線程的 daemon屬性 設置爲 True
#2、也可在創建Thread對象時通過daemon參數將其設爲後臺線程 即 守護線程。
#後臺線程有一個特徵,如果所有的前臺線程都死亡了,那麼後臺線程會自動死亡。
#Python 解釋器的垃圾回收線程就是典型的後臺線程。
import threading
#先定義一個函數 用於準備作爲線程執行體target參數函數來使用
def action(max):
'''
先定義一個函數 用於準備作爲線程執行體target參數函數來使用
循環輸出當前線程名稱100次
'''
for i in range(max):
print(threading.current_thread().name + ' ' + str(i))
t=threading.Thread(target=action,args=(100,),name='後臺線程')
# 將此線程設置成後臺線程,通過設置線程的daemon屬性爲 True
# 也可在創建Thread對象時通過daemon參數將其設爲後臺線程
#注意:如果要將某個線程設置爲後臺線程,則必須在該線程啓動之前進行設置。
#也就是說,將 daemon 屬性設爲 True,必須在 start() 方法調用之前進行,否則會引發 RuntimeError 異常。
t.daemon=True
t.start()
for i in range(10):
print(threading.current_thread().name + ' ' +str(i))
# =============================================================================
# #Lock類 和 Rlock類 互斥鎖 用來解決數據不同步的問題
# #threading模塊提供了 Lock 和 RLock 兩個類,都各自提供兩個方法來 加互斥鎖 和 解除互斥鎖
# =============================================================================
##########
#銀行取錢 數據不同步 問題示例
#先定義一個賬戶類,並且封裝賬戶編號 和 賬戶餘額
class Account:
'''封裝賬戶編號 和 餘額 這兩個成員屬性'''
def __init__(self,account_no,balance):
self.account_no = account_no
self.balance = balance
import threading
import time
#定義一個函數 來準備作爲線程的執行函數 來模擬取錢操作
def draw(account,draw_amount):
'''傳入參數 實例化賬戶 和 取錢金額;來實現取錢操作,如果實例化賬戶餘額足夠則取款成功,否則取款失敗'''
if account.balance >= draw_amount:
print(threading.current_thread().name + '取錢成功,吐出鈔票:' + str(draw_amount))
#time.sleep(0.2)
account.balance -= draw_amount
print('餘額爲:' + str(account.balance))
else:
print(threading.current_thread().name + '取錢失敗,餘額不足!')
#創建實例化 賬戶對象
acct= Account('1234567',1000)
#啓動兩個線程模擬對同一個實例化 的賬戶對象進行取錢操作
#多次運行,則會出現 餘額不足但仍取款成功 的問題
#原由線程的 run()方法不具有線程安全性
threading.Thread(name='甲', target=draw, args=(acct,800)).start()
threading.Thread(name='乙', target=draw, args=(acct,800)).start()
##########
#Lock類 和 RLock類 的相同: 加鎖和解鎖方法
#1、acquire(blocking=True, timeout=-1): 請求對 Lock 或 RLock 加鎖,其中 timeout 參數指定加鎖多少秒。
#2、release(): 釋放鎖。
#Lock 和 RLock 的不同:
#1、threading.Lock: #它是一個基本的鎖對象,每次只能鎖定一次,其餘的鎖請求,需等待鎖釋放後才能獲取。
#2、threading.RLock: #它代表可重入鎖(Reentrant Lock)。對於可重入鎖,在同一個線程中可以對它進行多次鎖定,也可以多次釋放。
#如果使用 RLock,那麼 acquire() 和 release() 方法必須成對出現。
#如果調用了 n 次 acquire() 加鎖,則必須調用 n 次 release() 才能釋放鎖。
import threading
help(threading.Lock)
help(threading.RLock)
#示例
#修改Account類,加鎖,來讓其線程安全
#修改的該類中,定義了一個 RLock 對象。在程序中實現 draw() 方法時,進入該方法開始執行後立即請求對 RLock 對象加鎖,
#當執行完 draw() 方法的取錢邏輯之後,程序使用 finally 塊來確保釋放鎖。
class Account:
def __init__(self,account_no, balance):
self.account_no = account_no
self._balance = balance
self.lock = threading.RLock()
def getBalance(self):
return self._balance
def draw(self,draw_amount):
#加鎖操作
#加鎖操作RLock較爲常用,注意加鎖方式一般爲 先加鎖,然後try操作,最後finally解鎖。
self.lock.acquire()
try:
if self._balance >= draw_amount:
print(threading.current_thread().name + '取錢成功,吐出鈔票:' + str(draw_amount))
time.sleep(0.001)
#取錢成功後修改餘額
self._balance -= draw_amount
print('\t餘額爲:' + str(self._balance))
else:
print(threading.current_thread().name + '取錢失敗,餘額不足!')
finally:
#解鎖操作
self.lock.release()
#定義函數 準備作爲線程的target參數執行函數 來模擬取錢操作
def draw(account,draw_amount):
account.draw(draw_amount)
acct=Account('123456789',1000)
threading.Thread(name='甲', target=draw, args=(acct,800)).start()
threading.Thread(name='乙', target=draw, args=(acct,800)).start()
##########
#死鎖
#當兩個線程相互等待對方釋放同步監視器時,就會發生死鎖。
#Py解釋器沒有監測,也沒有采取措施來處理死鎖情況,所以在進行多線程時應該採取措施避免出現死鎖。
#注意:一旦出現死鎖,整個程序既不會發生任何異常,也不會給出任何提示,只是所有線程都處於阻塞狀態,無法繼續。
#死鎖示例
#在系統中出現多個同步監視器的情況下,很容易發生死鎖
import threading
import time
class A:
def __init__(self):
self.lock=threading.RLock()
def foo(self,b):
try:
#加鎖操作
self.lock.acquire()
print('當前線程名:' + threading.current_thread().name + '進入A實例的foo()方法')
time.sleep(0.02)
print('當前線程名:' + threading.current_thread().name + '企圖調用B實例的last()方法')
#調用另一個類實例方法中也有加鎖解鎖操作,容易發生死鎖問題
b.last()
finally:
#解鎖操作
self.lock.release()
def last(self):
try:
self.lock.acquire()
print('進入了A類的last()方法內部')
finally:
self.lock.release()
class B:
def __init__(self):
self.lock=threading.RLock()
def bar(self,a):
try:
self.lock.acquire()
print('當前線程名:' + threading.current_thread().name + '進入了B實例的bar()方法')
time.sleep(0.2)
print('當前線程名:' + threading.current_thread().name + '企圖調用A實例的last()方法')
a.last()
finally:
self.lock.release()
def last(self):
try:
self.lock.acquire()
print('進入了B類的last()方法內部')
finally:
self.lock.release()
a=A()
b=B()
def init():
threading.current_thread().name='主線程'
a.foo(b)
print('進入了主線程之後')
def action():
threading.current_thread().name='副線程'
b.bar(a)
print('進入了副線程之後')
#以action()爲target參數函數啓動新線程
#兩個線程開啓 即出現死鎖問題。
#注意解決死鎖問題的方式有:1、避免多次加鎖 2、相同的加鎖順序 3、使用定時鎖 即調用acquire()方法加鎖時候指定timeout參數 4、死鎖檢測
threading.Thread(target=action).start()
#init() #若開啓則出現死鎖問題。
# =============================================================================
# #線程通信
# #當線程在系統中運行時,線程的調度具有一定的透明性,通常程序無法準確控制線程的輪換執行,如果有需要,Python 可通過線程通信來保證線程協調運行。
# #線程通信的實現方式:1、threading模塊的Condition類 2、queue隊列模塊 3、threading模塊的Event類
# =============================================================================
####################
#Condition類
#來自threading線程模塊
#Condition對象可以讓那些己經得到 Lock 對象卻無法繼續執行的線程釋放 Lock 對象,
#Condition對象也可以喚醒其他處於等待狀態的線程
import threading
help(threading.Condition)
threading.Condition.__doc__
#Condition 類提供瞭如下幾個方法:
#acquire([timeout]) / release(): 調用 Condition 關聯的 Lock 的 acquire() 或 release() 方法。
#wait([timeout]): 導致當前線程進入 Condition 的等待池等待通知並釋放鎖,直到其他線程調用該 Condition 的 notify() 或 notify_all() 方法來喚醒該線程。在調用該 wait() 方法時可傳入一個 timeout 參數,指定該線程最多等待多少秒。
#notify(): 喚醒在該 Condition 等待池中的單個線程並通知它,收到通知的線程將自動調用 acquire() 方法嘗試加鎖。如果所有線程都在該 Condition 等待池中等待,則會選擇喚醒其中一個線程,選擇是任意性的。
#notify_all(): 喚醒在該 Condition 等待池中等待的所有線程並通知它們。
#示例 使用Condition()對象方法實現線程通信
import threading
class Account:
'''
定義一個Account 類,提供 draw() 和 deposit() 兩個方法,分別對應於該賬戶的取錢和存款操作。
因爲這兩個方法可能需要併發修改 Account 類的 self.balance 成員變量的值,所以它們都使用 Lock 來控制線程安全。
除此之外,這兩個方法還使用了 Condition 的 wait() 和 notify_all() 來控制線程通信。
實現要求存款者和取錢者不斷地重複存款、取錢的動作,而且要求每當存款者將錢存入指定賬戶後,取錢者就立即取出該筆錢。
不允許存款者連續兩次存錢,也不允許取錢者連續兩次取錢。
'''
def __init__(self,account_no, balance):
#封裝賬戶編號 和 賬戶餘額 的兩個變量
self.account_no = account_no
self._balance = balance
self.cond = threading.Condition()
self._flag = False #定義代表是否已經存錢的旗幟
def getBalance(self):
#因爲賬戶餘額不允許隨便修改,所以只爲self._balance提供getter方法
return self._balance
def draw(self,draw_amount):
#加鎖操作 Condition類對象加鎖操作,
#Condition類對象的加鎖方法 相當於調用 Condition 綁定的Lock的acquire()
self.cond.acquire()
try:
if not self._flag:
#Condition類對象阻塞操作,將當前線程放入等待池 等待通知
self.cond.wait() #如果沒有存錢,則將取錢方法阻塞 並放入等待池等待通知
else:
print(threading.current_thread().name + '取錢:' + str(draw_amount))
self._balance -= draw_amount #取錢 並 餘額變動
print('賬戶餘額爲:' + str(self._balance))
self._flag = False #將標識賬戶是否已有存款的旗幟設爲 False
#Condition類對象喚醒操作, 喚醒等待池中的其他所有線程 並 通知他們
self.cond.notify_all() #喚醒等待池中的其他所有線程 並 通知他們
#解鎖操作 Condition類對象解鎖操作
finally:
self.cond.release()
def deposit(self, deposit_amount):
#加鎖操作 Condition類對象加鎖操作,
#Condition類對象的加鎖方法 相當於調用 Condition 綁定的Lock的acquire() self.cond.acquire()
self.cond.acquire()
try:
if self._flag:
#Condition類對象阻塞操作,將當前線程放入等待池 等待通知
self.cond.wait() #如果已經存錢,則將存錢方法阻塞 並放入等待喫等待通知
else:
print(threading.current_thread().name + '存款:' + str(deposit_amount))
self._balance += deposit_amount #存錢 並 餘額變動
print('賬戶餘額爲:' + str(self._balance))
self._flag = True #將標識賬戶是否已有存款的旗幟設爲 True
#Condition類對象喚醒操作, 喚醒等待池中的其他所有線程 並 通知他們
self.cond.notify_all() #喚醒等待池中的其他所有線程 並 通知他們
#解鎖操作 Condition類對象解鎖操作
finally:
self.cond.release()
'''
程序使用 Condition 的 wait() 和 notify_all() 方法進行控制,
對存款者線程而言,當程序進入 deposit() 方法後,如果 self._flag 爲 True,則表明賬戶中已有存款,
程序調用 Condition 的 wait() 方法被阻塞;否則,程序向下執行存款操作,
當存款操作執行完成後,系統將 self._flag 設爲 True,然後調用 notify_all() 來喚醒其他被阻塞的線程。
如果系統中有存款者線程,存款者線程也會被喚醒,但該存款者線程執行到 ① 號代碼處時再次進入阻塞狀態,
只有執行 draw() 方法的取錢者線程纔可以向下執行。
同理,取錢者線程的運行流程也是如此
'''
#定義一個函數,作爲線程的target參數執行函數。 模擬重複max次執行取錢操作
def draw_many(account, draw_amount,max):
for i in range(max):
account.draw(draw_amount)
#定義一個函數,作爲線程的target參數執行函數。 模擬重複max次執行存錢操作
def deposit_many(account,deposit_amount,max):
for i in range(max):
account.deposit(deposit_amount)
acct=Account('123456789',0) #實例化創建一個賬戶
threading.Thread(name='取錢者 X ', target=draw_many, args=(acct,800,100)).start()
threading.Thread(name='存款者 甲 ', target=deposit_many, args=(acct,800,100)).start()
threading.Thread(name='存款者 乙 ', target=deposit_many, args=(acct,800,100)).start()
threading.Thread(name='存款者 丙 ', target=deposit_many, args=(acct,800,100)).start()
####################
#queue隊列模塊
#掌握Queue阻塞隊列的特性後,利用Queue來實現線程通信
import queue
help(queue)
queue.__doc__
queue.__all__
dir(queue)
#queue模塊的三個隊列類的簡單介紹如下:
#queue.Queue(maxsize=0): 代表 FIFO(先進先出)的常規隊列,maxsize 可以限制隊列的大小。如果隊列的大小達到隊列的上限,就會加鎖,再次加入元素時就會被阻塞,直到隊列中的元素被消費。如果將 maxsize 設置爲 0 或負數,則該隊列的大小就是無限制的。
#queue.LifoQueue(maxsize=0): 代表 LIFO(後進先出)的隊列,與 Queue 的區別就是出隊列的順序不同。
#PriorityQueue(maxsize=0): 代表優先級隊列,優先級最小的元素先出隊列。
#這三個隊列類的屬性和方法基本相同, 它們都提供瞭如下屬性和方法:
#
#Queue.qsize(): 返回隊列的實際大小,也就是該隊列中包含幾個元素。
#Queue.empty(): 判斷隊列是否爲空。
#Queue.full(): 判斷隊列是否已滿。
#
#Queue.put(item, block=True, timeout=None):向隊列中放入元素。
#如果隊列己滿,且 block 參數爲 True(阻塞),當前線程被阻塞,
#timeout 指定阻塞時間,如果將 timeout 設置爲 None,則代表一直阻塞,直到該隊列的元素被消費;
#如果隊列己滿,且 block 參數爲 False(不阻塞),則直接引發 queue.FULL 異常。
#
#Queue.put_nowait(item): 向隊列中放入元素,不阻塞。相當於在上一個方法中將 block 參數設置爲 False。
#
#Queue.get(item, block=True, timeout=None):從隊列中取出元素(消費元素)。
#如果隊列已滿,且 block 參數爲 True(阻塞),當前線程被阻塞,
#timeout 指定阻塞時間,如果將 timeout 設置爲 None,則代表一直阻塞,直到有元素被放入隊列中;
#如果隊列己空,且 block 參數爲 False(不阻塞),則直接引發 queue.EMPTY 異常。
#
#Queue.get_nowait(item): 從隊列中取出元素,不阻塞。相當於在上一個方法中將 block 參數設置爲 False。
#示例 阻塞隊列線程
import queue
#先定義了一個大小爲 2 的 Queue,程序先向該隊列中放入兩個元素,此時隊列還沒有滿,兩個元素都可以被放入。
#當程序試圖放入第三個元素時,如果使用 put() 方法嘗試放入元素將會阻塞線程
bq=queue.Queue(2) #實例化創建一個長度爲2的先進先出常規隊列
bq.put('Python')
bq.put('python')
print('11111111')
#bq.put('Python') #阻塞線程
print('22222222')
#示例 掌握Queue阻塞隊列的特性後,利用Queue來實現線程通信
import threading
import time
import queue
def product(bq):
#定義函數 用於線程的target參數執行函數
#參數Queue隊列對象,函數內實現隊列的放入元素功能
str_tuple=('Python','Kotlin','Swift')
for i in range(9):
print(threading.current_thread().name + '生產者準備生產元組元素...')
time.sleep(0.2)
#Queue.put()方法 向隊列中放入元素 如果隊列已滿則阻塞,直到元素被Queue.get()方法消費
bq.put(str_tuple[i % 3]) #%求餘操作
print(threading.current_thread().name + '生產者生產元組元素完成!')
def consume(bq):
#定義函數 用於線程target參數執行函數
#參數Queue隊列對象,函數內實現隊列的取出元素消費功能
while True:
print(threading.current_thread().name + '消費者準備消費元組元素...')
time.sleep(0.2)
#Queue.get()方法 從隊列中取出元素 如果隊列已空則阻塞,直到元素被Queue.put()方法存入
t=bq.get()
print(threading.current_thread().name + '消費者消費 [%s] 元素完成!!!'%t)
#實例化創建一個容量爲1的常規隊列 先進先出 用作線程target參數執行函數的參數。
bq=queue.Queue(maxsize=1) #創建一個容量爲1的隊列
#啓動3個生產者 線程
threading.Thread(target=product, args=(bq,)).start()
threading.Thread(target=product, args=(bq,)).start()
threading.Thread(target=product, args=(bq,)).start()
#Queue 隊列的大小爲 1,因此三個生產者線程無法連續放入元素,必須等待消費者線程取出一個元素後,其中的一個生產者線程才能放入一個元素。
#三個生產者線程都想向 Queue 中放入元素,但只要其中一個生產者線程向該隊列中放入元素之後,其他生產者線程就必須等待,等待消費者線程取出 Queue 隊列中的元素。
#啓動1個消費 線程
threading.Thread(target=consume, args=(bq,)).start()
####################
#Event類 來自threading線程模塊
#Event類 是一種非常簡單的線程通信機制,一個線程發出一個 Event,另一個線程可通過該 Event 被觸發。
#Event類 本身管理一個內部旗標,
#程序可以通過 Event 的 set() 方法將該旗標設置爲 True,也可以調用 clear() 方法將該旗標設置爲 False。
#程序可以調用 wait() 方法來阻塞當前線程,直到 Event 的內部旗標被設置爲 True。
import threading
help(threading.Event)
threading.Event.__doc__
dir(threading.Event)
#Event 提供瞭如下方法:
#is_set(): 該方法返回 Event 的內部旗標是否爲True。
#set(): 該方法將會把 Event 的內部旗標設置爲 True,並喚醒所有處於等待狀態的線程。
#clear(): 該方法將 Event 的內部旗標設置爲 False,通常接下來會調用 wait() 方法來阻塞當前線程。
#wait(timeout=None): 該方法會阻塞當前線程。
#示例 Event的簡單用法
import threading
import time
event=threading.Event()
def cal(name):
#定義函數 準備用於線程target參數執行函數
#函數內實現 等待事件,進入等待阻塞狀態
print('%s 啓動\t' % threading.current_thread().getName())
print('%s 準備開始計算狀態' % name)
event.wait() #wait方法阻塞當前線程進入等待狀態
#收到事件後才能繼續進入運行狀態
print('%s 收到通知了\t' % threading.current_thread().getName())
print('%s 正式開始計算!' % name)
#創建並啓動2條線程,他們都會在啓動、準備開始計算狀態處 阻塞,等待。
threading.Thread(target=cal, args=('甲',)).start()
threading.Thread(target=cal, args=('乙',)).start()
time.sleep(2)
print('--------------------')
#發出事件 直到set方法設置Event內部旗幟爲True 並 喚醒等待線程開始繼續往下執行
print('主線程發出事件')
event.set() #set方法喚醒所有處於等待狀態的線程
#示例
#結合 Event 的內部旗標,同樣實現前面的 Account 的生產者-消費者效果:
#存錢線程(生產者)存錢之後,必須等取錢線程(消費者)取錢之後才能繼續向下執行。
#Event 實際上類似於 Condition 和旗標的結合體,但 Event 本身並不帶 Lock 對象,因此如果要實現線程同步,還需要額外的 Lock 對象。
import threading
class Account:
'''
定義一個Account 類,提供 draw() 和 deposit() 兩個方法,分別對應於該賬戶的取錢和存款操作。
實現要求存款者和取錢者不斷地重複存款、取錢的動作,而且要求每當存款者將錢存入指定賬戶後,取錢者就立即取出該筆錢。
不允許存款者連續兩次存錢,也不允許取錢者連續兩次取錢。
'''
#定義構造器 封裝賬戶編號、賬戶餘額兩個類屬性
def __init__(self,account_no,balance):
self.accout_no = account_no
self._balance = balance
self.lock = threading.Lock()
self.event = threading.Event()
#因爲賬戶餘額不允許隨便修改,所以只爲self._balance提供getter方法
def getBalance(self):
return self._balance
#定義取錢操作 線程加鎖的安全方式
def draw(self,draw_amount):
#加鎖操作
self.lock.acquire()
if self.event.is_set(): #is_set()方法返回 Event 的內部旗標是否爲True。
print(threading.current_thread().name + '取錢:' + str(draw_amount))
self._balance -= draw_amount
print('賬戶餘額爲:' + str(self._balance))
#clear方法設置Event內部旗幟爲False,通常接下來會調用 wait() 方法來阻塞當前線程。
self.event.clear() #clear方法設置Event內部旗幟爲False,通常接下來會調用 wait() 方法來阻塞當前線程。
self.lock.release() #解鎖操作
self.event.wait() #阻塞當前線程 進入等待狀態
else:
self.lock.release() #解鎖操作
self.event.wait() #阻塞當前線程 進入等待狀態
#定義存錢操作 線程加鎖的安全方式
def deposit(self,deposit_amount):
#加鎖操作
self.lock.acquire()
if not self.event.is_set():
print(threading.current_thread().name + '存款:' + str(deposit_amount))
self._balance += deposit_amount
print('賬戶餘額爲:' + str(self._balance))
#set方法將內部旗標設置爲 True,並喚醒所有處於等待狀態的線程
self.event.set()
self.lock.release() #解鎖操作
self.event.wait() #阻塞當前線程 進入等待狀態
else:
self.lock.release() #解鎖操作
self.event.wait() #阻塞當前線程 進入等待狀態
#定義一個函數,模擬重複max次執行取錢操作
def draw_many(account, draw_amount,max):
for i in range(max):
account.draw(draw_amount)
#定義一個函數,模擬重複max次執行存錢操作
def deposit_many(account,deposit_amount,max):
for i in range(max):
account.deposit(deposit_amount)
acct=Account('123456789',0) #實例化創建一個賬戶
threading.Thread(name='取錢者 X ', target=draw_many, args=(acct,800,100)).start()
threading.Thread(name='存款者 甲 ', target=deposit_many, args=(acct,800,100)).start()
threading.Thread(name='存款者 乙 ', target=deposit_many, args=(acct,800,100)).start()
threading.Thread(name='存款者 丙 ', target=deposit_many, args=(acct,800,100)).start()
# =============================================================================
# #線程池 ThreadPoolExecutor類
# #線程池的基類是 concurrent.futures 模塊中的 Executor,
# #Executor 提供了兩個子類,即 ThreadPoolExecutor 和 ProcessPoolExecutor,
# #其中 ThreadPoolExecutor類 用於創建線程池,而 ProcessPoolExecutor類 用於創建進程池。
# =============================================================================
'''
注意:
1、submit()線程池類方法提交線程池後返回的Future對象,用result()方法來獲取線程任務的運回值,
但該方法會阻塞當前主線程,只有等到錢程任務完成後,result() 方法的阻塞纔會被解除。
可通過 Future 的 add_done_callback() 方法來添加回調函數來避免直接調用result()方法阻塞線程
另外線程池類方法map()後可循環迭代每一個線程
'''
from concurrent.futures import ThreadPoolExecutor
help(ThreadPoolExecutor)
ThreadPoolExecutor.__doc__
dir(ThreadPoolExecutor)
#Exectuor類提供瞭如下常用方法:
#1、submit(fn, *args, **kwargs): 將 fn 函數提交給線程池。*args 代表傳給 fn 函數的參數,*kwargs 代表以關鍵字參數的形式爲 fn 函數傳入參數。
#2、map(func, *iterables, timeout=None, chunksize=1):該函數類似於全局函數 map(func, *iterables),
#只是該函數將會啓動多個線程,以異步方式立即對 iterables 執行 map 處理。
#3、shutdown(wait=True): 關閉線程池
#程序將 fn 函數提交(submit)給線程池後,submit 方法會返回一個 Future 對象,
#Future類主要用於獲取線程任務函數的返回值。
#由於線程任務會在新線程中以異步方式執行,因此,線程執行的函數相當於一個“將來完成”的任務,所以 Python 使用 Future 來代表。
#Future對象提供瞭如下方法:
#1、cancel(): 取消該 Future 代表的線程任務。如果該任務正在執行,不可取消,則該方法返回 False;否則,程序會取消該任務,並返回 True。
#2、cancelled(): 返回 Future 代表的線程任務是否被成功取消。
#3、running(): 如果該 Future 代表的線程任務正在執行、不可被取消,該方法返回 True。
#4、done(): 如果該 Funture 代表的線程任務被成功取消或執行完成,則該方法返回 True。
#5、result(timeout=None): 獲取該 Future 代表的線程任務最後返回的結果。
#如果 Future 代表的線程任務還未完成,該方法將會阻塞當前線程,
#其中 timeout 參數指定最多阻塞多少秒。
#6、exception(timeout=None): 獲取該 Future 代表的線程任務所引發的異常。如果該任務成功完成,沒有異常,則該方法返回 None。
#7、add_done_callback(fn): 爲該 Future 代表的線程任務註冊一個“回調函數”,當該任務成功完成時,程序會自動觸發該 fn 函數。
'''
使用線程池來執行線程任務的步驟如下:
1、調用 ThreadPoolExecutor 類的構造器創建一個線程池。
2、定義一個普通函數作爲線程任務。
3、調用 ThreadPoolExecutor 對象的 submit() 方法來提交線程任務。
4、當不想提交任何任務時,調用 ThreadPoolExecutor 對象的 shutdown() 方法來關閉線程池。
'''
###################
#示例
#示例如何使用線程池來執行線程任務
from concurrent.futures import ThreadPoolExecutor
import threading
import time
def action(max):
#定義一個函數 準備用作線程池fn參數函數
my_sum = 0
for i in range(max):
print(threading.current_thread().name + ' ' + str(i))
my_sum += 1
return my_sum
#實例化創建線程池 參數max_workers指定工作線程數量爲2條
pool=ThreadPoolExecutor(max_workers=2) #創建一個包含2條線程的線程池
#類方法submit() 將fn參數函數提交給線程池
future1=pool.submit(action,50) #向線程池提交一個fn參數函數action,50爲函數參數
future2=pool.submit(action,100) #繼續向線程池提交一個fn參數函數action,100爲函數參數
type(future1) #查看 實例化提交線程池以後的類型
#類方法done() 判斷提交線程池後的future對象 即線程任務 是否運行完成
time.sleep(1)
print(future1.done()) #輸出future對象是否運行完畢
time.sleep(1)
print(future2.done()) #輸出future對象是否運行完畢
#類方法result() 返回提交線程池後的future對象 即線程任務 的程序運行結果
#注意提交線程池後返回的Future對象,用result()方法來獲取線程任務的運回值,
#但該方法會阻塞當前主線程,只有等到錢程任務完成後,result() 方法的阻塞纔會被解除。
#可通過 Future 的 add_done_callback() 方法來添加回調函數來避免直接調用result()方法阻塞線程
print(future1.result()) #輸出future對象的程序運行結果
print(future2.result()) #輸出future對象的程序運行結果
#類方法shutdown() 關閉線程池
pool.shutdown() #線程池關閉
####################
#示例 add_done_callback() 提交線程池後的Future對象方法 添加回調函數
#通過 Future 的 add_done_callback() 方法來添加回調函數來避免直接調用result()方法阻塞線程
from concurrent.futures import ThreadPoolExecutor
import threading
import time
def action(max):
#定義一個函數 準備用作線程池fn參數函數
my_sum = 0
for i in range(max):
print(threading.current_thread().name + ' ' + str(i))
my_sum += i
return my_sum
with ThreadPoolExecutor(max_workers=2) as pool:
future1 = pool.submit(action,50)
future2 = pool.submit(action,100)
def get_result(future):
print(future.result())
future1.add_done_callback(get_result)
future2.add_done_callback(get_result)
print('--------------------')
'''
由於程序並未直接調用 future1、future2 的 result() 方法,因此主線程不會被阻塞,
可以立即看到輸出主線程打印出的橫線。
接下來將會看到兩個新線程併發執行,當線程任務執行完成後,get_result() 函數被觸發,輸出線程任務的返回值。
'''
####################
#示例 線程池類方法 map(func, *iterables, timeout=None, chunksize=1)
#該方法的功能類似於全局函數 map(),區別在於線程池的 map() 方法會爲 iterables 的每個元素啓動一個線程,以併發方式來執行 func 函數。
#這種方式相當於啓動 len(iterables) 個線程,井收集每個線程的執行結果。
from concurrent.futures import ThreadPoolExecutor
import threading
import time
def action(max):
#定義一個函數 準備用作線程池fn參數函數
my_sum = 0
for i in range(max):
print(threading.current_thread().name + ' ' + str(i))
my_sum += i
return my_sum
with ThreadPoolExecutor(max_workers=4) as pool:
results = pool.map(action,(50,100,150)) #啓動3個線程
print('---------------')
for r in results: #for循環迭代每個線程及結果
print(r)
# =============================================================================
# #線程局部變量
# #線程局部變量爲每一個使用該變量的線程都提供一個變量的副本,使每一個線程都可以獨立地改變自己的副本,而不會和其他線程的副本衝突。
# #從線程的角度看,就好像每一個線程都完全擁有該變量一樣。
# =============================================================================
#示例 threading.local()來賦值聲明線程局部變量
#作用好像每個線程都完成擁有該變量亦一樣
from concurrent.futures import ThreadPoolExecutor
import threading
mydata = threading.local() #定義線程局部變量
def action(max):
for i in range(max):
try:
mydata.x += i
except:
mydata.x = i
print('%s mydata.x 的值爲: %d' %(threading.current_thread().name, mydata.x))
with ThreadPoolExecutor(max_workers=2) as pool:
pool.submit(action,10)
pool.submit(action,10)
'''
程序中作爲線程執行體的 action 函數使用 mydata.x 記錄 0~10 的累加值,
如果兩個線程共享同一個 mydata 變量,將會看到 mydata.x 最後會累加到 90(0~9 的累加值是 45,但兩次累加會得到 90)。
但由於 mydata 是 threading.local 變量,因此程序會爲每個線程都創建一個該變量的副本,所以將會看到兩個線程的 mydata.x 最後都累加到 45。
'''
# =============================================================================
# #線程定時器
# #線程定時器是線程類threading類的Timer子類,該子類可用於控制指定函數在特定時間內執行一次
# =============================================================================
'''
注意:
#Timer定時器只能控制函數在指定時間內執行一次,
#如果要使用 Timer 控制函數多次重複執行,則需要函數內嵌套定時器 來循環執行下一次調度。
'''
from threading import Timer
help(Timer)
Timer.__doc__
dir(Timer)
###################
#示例 定時器Timer 控制 10s 後執行 hello 函數。
from threading import Timer
def hello():
print('hello,world')
#創建一個定時器線程
t=Timer(10,hello) #定時10秒以後執行hello函數
t.start() #定時器線程啓動
###################
#示例 循環調用定時器
#Timer定時器只能控制函數在指定時間內執行一次,
#如果要使用 Timer 控制函數多次重複執行,則需要函數內嵌套定時器 來循環執行下一次調度。
from threading import Timer
import time
count=0
def print_time():
'''
定義函數,用於Timer()定時器中參數函數
函數中嵌套Timer()定時器,可循環判斷,如果count<10,則循環調用10次定時器輸出當前時間
'''
print('當前時間:%s' % time.ctime())
global t,count
count+=1
if count<10:
t=Timer(1, print_time)
t.start()
t = Timer(1,print_time)
t.start()
# =============================================================================
# #sched模塊 任務調度模塊
# #sched模塊 可以比 Timer線程定時器 執行更復雜的任務調度
# ##sched模塊提供了 sched.scheduler 類,該類代表一個任務調度器。
# =============================================================================
'''
使用:
1、實例化任務調度器 sched.scheduler()
2、實例化任務調度器方法:絕對時間執行函數任務、相對時間執行函數任務、取消任務等
'''
import sched
help(sched)
sched.__doc__
sched.__all__ #只有一個方法 scheduler() 定義線程調度器
dir(sched)
#sched.scheduler(timefunc=time.monotonic, delayfunc=time.sleep) 構造器支持兩個參數:
#1、timefunc: 該參數指定生成時間戳的時間函數,默認使用 time.monotonic 來生成時間戳。
#2、delayfunc: 該參數指定阻塞程序的函數,默認使用 time.sleep 函數來阻塞程序。
help(sched.scheduler)
dir(sched.scheduler)
help(sched.scheduler.enterabs) #絕對時間執行函數任務
help(sched.scheduler.enter) #相對時間執行函數任務
help(sched.scheduler.run) #運行所有需要調度的任務
help(sched.scheduler.queue) #返回調度器的調度隊列,根據任務優先級
#sched.scheduler 調度器支持如下常用屬性和方法:
#1、scheduler.enterabs(time, priority, action, argument=(), kwargs={}):
#指定在 time 時間點執行 action 函數,argument 和 kwargs 都用於向 action 函數傳入參數,其中 argument 使用位置參數的形式傳入參數,kwargs 使用關鍵字參數的形式傳入參數。
#該方法返回一個 event,它可作爲 cancel() 方法的參數用於取消該調度。
#priority 參數指定該任務的優先級,當在同一個時間點有多個任務需要執行時,優先級高(值越小代表優先級越高)的任務會優先執行。
#2、scheduler.enter(delay, priority, action, argument=(),kwargs={}):
#該方法與上一個方法基本相同,只是 delay 參數用於指定多少秒之後執行 action 任務。
#3、scheduler.cancel(event): 取消任務。如果傳入的 event 參數不是當前調度隊列中的 event,程序將會引發 ValueError 異常。
#4、scheduler.empty(): 判斷當前該調度器的調度隊列是否爲空。
#5、scheduler.run(blocking=True): 運行所有需要調度的任務。
#如果調用該方法的 blocking 參數爲 True,該方法將會阻塞線程,直到所有被調度的任務都執行完成。
#6、scheduler.queue: 該只讀屬性返回該調度器的調度隊列。
###################
#示例 sched.scheduler執行任務調度
import sched
import time
import threading
def print_time(name='default'):
print('%s的時間:%s' % (name,time.ctime()))
print('主線程開始時間:',time.ctime())
#實例化創建 線程調度器
s=sched.scheduler()
#enter()方法 相對時間 任務調度
s.enter(10,1,print_time) #參數1指定10秒後開始運行,參數2位運行優先等級爲1,參數3爲調度運行函數
s.enter(5,2,print_time, argument=('位置參數',)) #參數1指定10秒後開始運行,參數2位運行優先等級爲1,參數3爲調度運行函數,參數4爲參數3函數的元組參數
s.enter(5,1,print_time, kwargs={'name':'字典參數'}) #參數1指定10秒後開始運行,參數2位運行優先等級爲1,參數3爲調度運行函數,參數3爲參數3函數的字典參數
#run()方法 運行所有需要調度的任務
s.run()
print('主線程結束時間:',time.ctime())
# =============================================================================
# #multiprocessding模塊 進程封裝
# =============================================================================
'''
注意:
1、multiprocessing.Process類通過實例化對象指定target參數執行函數 在IDLE裏不顯示函數執行內容。
通過在控制檯通過執行文件可顯示target參數執行函數的內容。
2、通過 multiprocessing.Process 來創建並啓動進程時,程序必須先判斷if __name__=='__main__':,否則可能引發異常。
'''
#探索模塊
import multiprocessing
help(multiprocessing)
multiprocessing.__doc__
multiprocessing.__all__
dir(multiprocessing)
####################
#multiprocessing.Process類 進程創建
#Python 在 multiprocessing模塊下提供了 Process 來創建新進程。
#與 Thread 類似的是,使用 Process 創建新進程也有兩種方式:
#1、使用 multiprocessing模塊 中的 Process類 來實例化對象創建新進程,並指定函數作爲target參數函數
#2、繼承 Process類 ,並重寫他的 run() 方法來創建進程類,程序創建Process子類的實例作爲進程
import multiprocessing
help(multiprocessing.Process)
dir(multiprocessing.Process)
#Process 類也有如下類似的方法和屬性:
#run(): 重寫該方法可實現進程的執行體。
#start(): 該方法用於啓動進程。
#join([timeout]): 該方法類似於線程的 join() 方法,當前進程必須等待被 join 的進程執行完成才能向下執行。
#name: 該屬性用於設置或訪問進程的名字。
#is_alive(): 判斷進程是否還活着。
#daemon: 該屬性用於判斷或設置進程的後臺狀態。
#pid: 返回進程的 ID。
#authkey: 返回進程的授權 key。
#terminate(): 中斷該進程。
help(multiprocessing.Process.start)
help(multiprocessing.Process.run)
help(multiprocessing.Process.daemon)
help(multiprocessing.Process.join)
#示例 通過實例化創建進程 指定target參數執行函數
#注意:
#1、multiprocessing.Process類通過實例化對象指定target參數執行函數 在IDLE裏不顯示函數執行內容。
#通過在控制檯通過執行文件可顯示target參數執行函數的內容。
import multiprocessing
import os
def action(max):
for i in range(max):
print('(%s)子進程 (父進程:(%s)):%d' %(os.getpid(), os.getppid(), i))
if __name__ == '__main__':
for i in range(100):
print('(%s)主進程:%d' %(os.getpid(), i))
if i == 20:
np1=multiprocessing.Process(target=action, args=(100,))
np1.start()
np2=multiprocessing.Process(target=action, args=(100,))
np2.start()
np2.join()
print('主進程執行完畢')
#示例 繼承Process類來創建子進程
#注意:
#1、multiprocessing.Process類通過實例化對象指定target參數執行函數 在IDLE裏不顯示函數執行內容。
#通過在控制檯通過執行文件可顯示target參數執行函數的內容。
import multiprocessing
import os
class MyProcess(multiprocessing.Process):
def __init__(self,max):
self.max=max
super().__init__()
#重寫run方法作爲進程執行體
def run(self):
for i in range(self.max):
print('(%s)子進程(父進程:(%s)):%d' %(os.getpid(), os.getppid(), i))
if __name__ == '__main__':
for i in range(100):
print('(%s)主進程:%d' %(os.getpid(),i))
if i == 20:
mp1=MyProcess(100)
mp1.start()
mp2=MyProcess(100)
mp2.start()
mp2.join()
print('主進程執行完畢!')
# =============================================================================
# #進程啓動的三種方式 spawn(windows僅支持此方式)、fork、forkserver
# #spawn: 父進程啓動一個全新的python解釋器(windows平臺僅支持此方式啓動進程)
# #fork: 父進程使用 os.fork() 來啓動一個 Python 解釋器進程。
# #forkserver: 如果使用這種方式來啓動進程,程序將會啓動一個服務器進程。
# =============================================================================
'''
注意:
1、multiprocessing.set_start_method(' ')函數用於設置啓動進程的方式,設置代碼必須放在多進程代碼之前。
2、window只支持spawn方式啓動進程
'''
#示例
import multiprocessing
import os
#help(multiprocessing.set_start_method)
def foo(q):
'''參數q爲一個隊列,可在隊列內存取元素'''
print('被啓動的新進程: (%s)' % os.getpid())
q.put('Python')
if __name__ == '__main__':
#設置使用fork方式啓動進程
multiprocessing.set_start_method('spawn')
#創建一個進程的隊列
q = multiprocessing.Queue()
#創建並啓動子進程
mp = multiprocessing.Process(target=foo, args=(q, ))
mp.start()
#獲取隊列中的消息
print(q.get())
mp.join()
# =============================================================================
# #進程池 multiprocessing.Pool類
# #通過 multiprocessing 模塊的 Pool類來實例化創建進程池,然後通過進程池方法提交進程池、關閉進程池、等待進程池等
# =============================================================================
import multiprocessing
help(multiprocessing.Pool)
multiprocessing.Pool.__doc__
dir(multiprocessing.Pool)
#進程池具有如下常用方法:
#1、apply(func[, args[, kwds]]): 將 func 函數提交給進程池處理。其中 args 代表傳給 func 的位置參數,kwds 代表傳給 func 的關鍵字參數。
#該方法會被阻塞直到 func 函數執行完成。
#2、apply_async(func[, args[, kwds[, callback[, error_callback]]]]):
#這是 apply() 方法的異步版本,該方法不會被阻塞。其中 callback 指定 func 函數完成後的回調函數,error_callback 指定 func 函數出錯後的回調函數。
#3、map(func, iterable[, chunksize]): 類似於 Python 的 map() 全局函數,只不過此處使用新進程對 iterable 的每一個元素執行 func 函數。
#4、map_async(func, iterable[, chunksize[, callback[, error_callback]]]):
#這是 map() 方法的異步版本,該方法不會被阻塞。其中 callback 指定 func 函數完成後的回調函數,error_callback 指定 func 函數出錯後的回調函數。
#5、imap(func, iterable[, chunksize]): 這是 map() 方法的延遲版本。
#6、imap_unordered(func, iterable[, chunksize]):
#功能類似於 imap() 方法,但該方法不能保證所生成的結果(包含多個元素)與原 iterable 中的元素順序一致。
#7、starmap(func, iterable[,chunksize]): 功能類似於 map() 方法,但該方法要求 iterable 的元素也是 iterable 對象,程序會將每一個元素解包之後作爲 func 函數的參數。
#8、close(): 關閉進程池。在調用該方法之後,該進程池不能再接收新任務,它會把當前進程池中的所有任務執行完成後再關閉自己。
#9、terminate(): 立即中止進程池。
#10、join(): 等待所有進程完成。
#示例 實例化創建進程池
#使用 apply_async() 方法啓動進程:
import multiprocessing
import time
import os
#定義一個函數,準備用作進程池func參數函數
def action(name='default'):
print('(%s)進程正在執行,參數爲:%s'%(os.getpid(), name))
time.sleep(3)
#通過 multiprocessing.Process 來創建並啓動進程時,程序必須先判斷if __name__=='__main__':,否則可能引發異常。
if __name__ == '__main__':
#實例化創建進程池
pool=multiprocessing.Pool(processes=4)
#異步提交進程池
pool.apply_async(action)
pool.apply_async(action,args=('位置參數',))
pool.apply_async(action,kwds={'name':'字典參數'})
#關閉進程池,不再接受新任務,會等待進程池中所有任務完成後關閉
pool.close()
#等待所有進程完成
pool.join()
#示例 實例化創建進程池 通過上下文管理協議創建
#使用map()方法來啓動進程
import multiprocessing
import time
import os
#定義一個函數,準備用作進程池func參數函數
def action(max):
my_sum=0
for i in range(max):
print('(%s)進程正在執行:%d'%(os.getpid(), i))
my_sum+=i
return my_sum
#通過 multiprocessing.Process 來創建並啓動進程時,程序必須先判斷if __name__=='__main__':,否則可能引發異常。
if __name__ == '__main__':
#上下文管理器創建進程池 進程數量爲4
with multiprocessing.Pool(processes=4) as pool:
#提交併啓動三個進程
results=pool.map(action,(50,100,150))
print('-----------------')
#for循環迭代每個進程及結果
for r in results:
print(r)
# =============================================================================
# #進程通信 multiprocessing.Queue類 和 multiprocessing.Pipe類
#
# #Python 爲進程通信提供了兩種機制:
# #Queue:一個進程向 Queue 中放入數據,另一個進程從 Queue 中讀取數據。
# #Pipe:Pipe 代表連接兩個進程的管道。程序在調用 Pipe() 函數時會產生兩個連接端,分別交給通信的兩個進程,接下來進程既可從該連接端讀取數據,也可向該連接端寫入數據。
# =============================================================================
'''
注意:
進程通信中,使用實例化創建multiprocessing.Pipe類管道對象返回兩個PipeConnection對象,
其中前一個PipeConnection對象用於接受數據,後一個PipeConnection對象用於發送數據
'''
import multiprocessing
help(multiprocessing.Queue)
help(multiprocessing.Pipe)
##########
#Queue:一個進程向 Queue 中放入數據,另一個進程從 Queue 中讀取數據。
dir(multiprocessing.Queue)
#multiprocessing 模塊下的 Queue 和 queue 模塊下的 Queue 基本類似,
#它們都提供了 qsize()、empty()、full()、put()、put_nowait()、get()、get_nowait() 等方法。
#區別只是 multiprocessing 模塊下的 Queue 爲進程提供服務,而 queue 模塊下的 Queue 爲線程提供服務。
#示例 使用multiprocessing.Queue類來實現進程之間的通信
import multiprocessing
#
def f(q):
'''
創建一個函數,用作進程target參數的執行函數
參數q爲一個實例化創建的multiprocessing.Queue類對象
'''
print('(%s)進程開始放入數據...'%multiprocessing.current_process().pid)
#向隊列中放入元素
q.put('血皇敖天')
if __name__ == "__main__":
#實例化創建進程通信的隊列
q=multiprocessing.Queue()
#實例化創建並啓動子進程運行函數
p=multiprocessing.Process(target=f, args=(q,))
p.start()
print('(%s)進程開始取出數據...'%multiprocessing.current_process().pid)
#從進程通信的隊列中取出元素
print(q.get())
#插入實現子進程
p.join()
##########
#Pipe:Pipe 代表連接兩個進程的管道。程序在調用 Pipe() 函數時會產生兩個連接端,分別交給通信的兩個進程,接下來進程既可從該連接端讀取數據,也可向該連接端寫入數據。
dir(multiprocessing.Pipe)
#使用 Pipe 實現進程通信,程序會調用 multiprocessing.Pipe() 函數來創建一個管道,
#該函數會返回兩個 PipeConnection 對象,代表管道的兩個連接端(一個管道有兩個連接端,分別用於連接通信的兩個進程)。
#PipeConnection 對象包含如下常用方法:
#1、send(obj): 發送一個 obj 給管道的另一端,另一端使用 recv() 方法接收。需要說明的是,該 obj 必須是可 picklable 的(Python 的序列化機制),如果該對象序列化之後超過 32MB,則很可能會引發 ValueError 異常。
#2、recv(): 接收另一端通過 send() 方法發送過來的數據。
#3、fileno(): 關於連接所使用的文件描述器。
#4、close(): 關閉連接。
#5、poll([timeout]): 返回連接中是否還有數據可以讀取。
#6、send_bytes(buffer[, offset[, size]]):
#發送字節數據。如果沒有指定 offset、size 參數,則默認發送 buffer 字節串的全部數據;如果指定了 offset 和 size 參數,則只發送 buffer 字節串中從 offset 開始、長度爲 size 的字節數據。通過該方法發送的數據,應該使用 recv_bytes() 或 recv_bytes_into 方法接收。
#7、recv_bytes([maxlength]):
#接收通過 send_bytes() 方法發迭的數據,maxlength 指定最多接收的字節數。該方法返回接收到的字節數據。
#8、recv_bytes_into(buffer[, offset]):
#功能與 recv_bytes() 方法類似,只是該方法將接收到的數據放在 buffer 中。
#示例 使用multiprocessing.Pipe類來實現進程之間的通信
import multiprocessing
def f(conn):
'''
創建一個函數,用作進程target參數的執行函數
參數conn爲實例化創建的multiprocessing.Pipe類管道對象返回的兩個PipeConnection對象中的後一個用於發送數據的對象
'''
print('(%s)進程開始發送數據...'%multiprocessing.current_process().pid)
conn.send('血皇敖天')
if __name__ =='__main__':
#實例化創建進程通信的pipe管道
#返回兩個PipeConnection對象,前一個用於接收數據,後一個用於發送數據
parent_conn, child_conn = multiprocessing.Pipe()
#實例化創建並啓動子進程 來執行函數 從而發送管道數據
p=multiprocessing.Process(target=f, args=(child_conn,))
p.start()
print('(%s)進程開始接收數據...'%multiprocessing.current_process().pid)
#前一個生成返回的實例化對象用於接受數據
print(parent_conn.recv())
p.join()
# =============================================================================
# #併發及異步併發爬蟲
# #concurrent.futures.ThreadPoolExecutor 線程池類、
# #concurrent.futures.ProcessPoolExecutor 進程池類
# #asyncio模塊 異步併發模塊
# #aiohttp模塊 異步的requests
# =============================================================================
'''
Py併發編程Futures併發爬蟲.py
Py併發編程asyncio模塊異步併發爬蟲.py
深入:
asyncio模塊 異步併發模塊
aiohttp模塊 異步的requests
'''
#示例 單線程簡易爬蟲
import requests
import time
def download_one(url):
resp = requests.get(url)
print('Read {} from {}'.format(len(resp.content), url))
def download_all(sites):
for site in sites:
download_one(site)
def main():
sites = [
'http://c.biancheng.net',
'http://c.biancheng.net/c',
'http://c.biancheng.net/python'
]
start_time = time.perf_counter()
download_all(sites)
end_time = time.perf_counter()
print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))
if __name__ == '__main__':
main()
####################
#示例 多線程/多進程 簡易爬蟲
import concurrent.futures
import requests
import threading
import time
def download_one(url):
resp = requests.get(url)
print('Read {} from {}'.format(len(resp.content), url))
def download_all(sites):
# '''多線程版本'''
# with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
# executor.map(download_one, sites)
'''多進程版本'''
# ProcessPoolExecutor() 表示類實例化創建進程池,使用多個進程並行的執行程序。
#通常省略參數 workers,因爲系統會自動返回 CPU 的數量作爲可以調用的進程數。
with concurrent.futures.ProcessPoolExecutor() as executor:
executor.map(download_one, sites)
def main():
sites = [
'http://c.biancheng.net',
'http://c.biancheng.net/c',
'http://c.biancheng.net/python'
]
start_time = time.perf_counter()
download_all(sites)
end_time = time.perf_counter()
print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))
if __name__ == '__main__':
main()
####################
#示例 多線程迭代器 簡易爬蟲
import concurrent.futures
import requests
import time
def download_one(url):
resp = requests.get(url)
print('Read {} from {}'.format(len(resp.content), url))
def download_all(sites):
with concurrent.futures.ThreadPoolExecutor(max_workers=5) as executor:
to_do = []
for site in sites:
future = executor.submit(download_one, site)
to_do.append(future)
#as_completed(fs) 針對給定的 future 迭代器 fs,在其完成後返回完成後的迭代器。
for future in concurrent.futures.as_completed(to_do):
future.result()
def main():
sites = [
'http://c.biancheng.net',
'http://c.biancheng.net/c',
'http://c.biancheng.net/python'
]
start_time = time.perf_counter()
download_all(sites)
end_time = time.perf_counter()
print('Download {} sites in {} seconds'.format(len(sites), end_time - start_time))
if __name__ == '__main__':
main()
####################
'''
異步併發爬蟲 需後期深入:
asyncio模塊 異步併發模塊
aiohttp模塊 異步的requests
'''
# =============================================================================
# #GIL 全局解釋器鎖
# #GIL 限制了 Python 多線程的性能,其本質上類似操作系統的 Mutex。
# #GIL 的功能是:在 CPython 解釋器中執行的每一個 Python 線程,都會先鎖住自己,以阻止別的線程執行。
# =============================================================================
'''
#注意:
GIL 不能絕對保證線程安全
因爲即便 GIL 僅允許一個 Python 線程執行,但Python 還有 check interval 這樣的搶佔機制。
'''
#示例 單線程 測速
import time
start = time.time()
def CountDown(n):
while n > 0:
n -= 1
CountDown(100000)
print("Time used:",(time.time() - start))
#示例 多線程 是否會加速
#結果顯示並 比單線程計算 沒有 提速;緣由是CPython 解釋器存在Gil全局解釋器鎖,
#GIL 的功能是:在 CPython 解釋器中執行的每一個 Python 線程,都會先鎖住自己,以阻止別的線程執行。
import time
from threading import Thread
start = time.time()
def CountDown(n):
while n > 0:
n -= 1
t1 = Thread(target=CountDown, args=[100000 // 2])
t2 = Thread(target=CountDown, args=[100000 // 2])
t1.start()
t2.start()
t1.join()
t2.join()
print("Time used:",(time.time() - start))
#CPython 使用引用計數來管理內容 即: 垃圾回收機制
#所有 Python 腳本中創建的實例,都會配備一個引用計數,來記錄有多少個指針來指向它。
#當實例的引用計數的值爲 0 時,會自動釋放其所佔的內存。
#示例
import sys
a=[]
b=a
print(sys.getrefcount(a)) #輸出引用計數值,此處爲3
#注意:GIL 不能絕對保證線程安全
#因爲即便 GIL 僅允許一個 Python 線程執行,但Python 還有 check interval 這樣的搶佔機制。
#示例:
import threading
n = 0
def foo():
global n
n += 1
threads = []
for i in range(100):
t = threading.Thread(target=foo)
threads.append(t)
for t in threads:
t.start()
for t in threads:
t.join()
print(n)
'''
執行此代碼會發現,其大部分時候會打印 100,但有時也會打印 99 或者 98,
原因在於 n+=1 這一句代碼讓線程並不安全。
'''
# =============================================================================
# #垃圾回收機制 gc模塊 手動啓動垃圾回收機制
# #垃圾回收機制引用計數機制
# #即:對象的引用計數值爲 0 時,說明這個對象永不再用,自然它就變成了垃圾,需要被回收。
# =============================================================================
import psutil #獲取系統信息模塊
import os
help(psutil)
psutil.__doc__
psutil.__all__
dir(psutil)
help(psutil.Process)
psutil.Process.__doc__
dir(psutil.Process)
help(psutil.Process.memory_full_info)
psutil.Process.memory_full_info.__doc__
####################
#示例 通過函數內部局部變量是否垃圾回收 來查看內存佔用空間
import os
import psutil #獲取系統信息模塊
#顯示當前 python 程序佔用的內存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss / 1024 / 1024
print('{} memory used: {} MB'.format(hint, memory))
def func():
show_memory_info('initial') #輸出較小內存佔用空間
#global a #如果將局部變量聲明成全局變量,則持續佔用較大內存空間
a = [i for i in range(10000000)] #臨時變量佔用較大內存空間。 函數運行完畢後即垃圾回收機制清除臨時變量
show_memory_info('after a created') #輸出垃圾回收機制後的較小內存佔用空間
#return a #如果函數返回局部變量,則也會持續佔用較大內存空間
#輸出佔用內存空間
func()
show_memory_info('finished')
####################
#示例 python內部的引用計數機制
import sys
a = []
#sys.getrefcount() 函數用於查看一個變量的引用次數
print(sys.getrefcount(a)) # 兩次引用,一次來自a,一次來自 getrefcount
def func(a):
print(sys.getrefcount(a)) # 四次引用,a,python的函數調用棧,函數參數,和 getrefcount
func(a)
print(sys.getrefcount(a)) # 兩次引用,一次來自a,一次來自getrefcount。函數func調用已經不存在被垃圾回收了
#####################
#示例 手動啓動垃圾回收
import gc #gc(garbage collector)模塊的引用計數技術來進行垃圾回收
import psutil #獲取系統信息模塊
#顯示當前 python 程序佔用的內存大小
def show_memory_info(hint):
pid = os.getpid()
p = psutil.Process(pid)
info = p.memory_full_info()
memory = info.uss / 1024 / 1024
print('{} memory used: {} MB'.format(hint, memory))
def func():
show_memory_info('initial') #輸出較小內存佔用空間
#global a #如果將局部變量聲明成全局變量,則持續佔用較大內存空間
a = [i for i in range(10000000)] #臨時變量佔用較大內存空間。 函數運行完畢後即垃圾回收機制清除臨時變量
show_memory_info('after a created') #輸出垃圾回收機制後的較小內存佔用空間
#return a #如果函數返回局部變量,則也會持續佔用較大內存空間
show_memory_info('initial')
a = [i for i in range(10000000)]
show_memory_info('after a created')
del a
gc.collect() #gc(garbage collector)模塊的引用計數技術來進行垃圾回收
show_memory_info('finish')
print(a)
####################
#示例 手動啓動垃圾回收 循環調用嵌套示例
#Python使用標記清除(mark-sweep)算法和分代收集(generational),來啓用針對循環引用的自動垃圾回收。
import gc
#循環引用嵌套,導致其a,b的引用計數不爲0,從而垃圾會機制不成立,需要手動顯示啓動垃圾回收機制。
def func():
show_memory_info('initial')
a = [i for i in range(10000000)]
b = [i for i in range(10000000)]
show_memory_info('after a, b created')
a.append(b)
b.append(a)
func()
gc.collect() #這裏如果不進行手動啓動垃圾回收,則內存暫用空間依然高企
show_memory_info('finished')
Py併發編程及應用(多線程、多進程;異步、全局鎖等)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.