一、線程
Python 中爲我們提供了兩個模塊來創建線程。
- _thread
- threading
thread 模塊已被廢棄。用戶可以使用 threading 模塊代替。所以,在 Python 中不能再使用"thread" 模塊。爲了兼容性,Python 將 thread 重命名爲 “_thread”。
相對 _thread 模塊來說, threading 模塊更加高級也更加常用。
多線程
創建多線程步驟:
- 導入線程庫
- 創建任務函數
- 創建線程對象
- 啓動線程
實例:
import threading,time
def task():
for i in range(1,11):
time.sleep(1)
print(threading.current_thread().name,end="")
print(":output >%d"%i)
print(time.ctime())
# 創建兩個線程
pt1=threading.Thread( target=task )
pt2=threading.Thread( target=task )
# 輸出線程名
print(threading.current_thread().name)
# 啓動線程
pt1.start()
pt2.start()
pt1.join()
pt2.join()
print(time.ctime())
輸出:
Sat Mar 14 16:07:12 2020
MainThread
Thread-2:output >1
Thread-1:output >1
Thread-1Thread-2:output >2
:output >2
Thread-1Thread-2:output >3
:output >3
Thread-1Thread-2:output >4
:output >4
Thread-1:output >5
Thread-2:output >5
Thread-1Thread-2:output >6
:output >6
Thread-2:output >7
Thread-1:output >7
Thread-2Thread-1:output >8
:output >8
Thread-2:output >9
Thread-1:output >9
Thread-1:output >10
Thread-2:output >10
Sat Mar 14 16:07:22 2020
可以看到一共用了10秒時間,兩個線程都完成了自己的任務。
使用 threading.Thread( )
創建線程時,可以傳入六個參數,比如線程名 name,任務函數參數 args等。
def __init__(self, group=None, target=None, name=None,
args=(), kwargs=None, *, daemon=None):
其中,如name、daemon等參數除了在創建線程時指定以外,還可以通過對象調用函數設置。 pt.setName()、pt.setDaemon()。
線程同步
在多線程中會常會發生多個線程同時訪問同一個資源的情況,造成線程不安全。而我們要求線程對臨界資源的操作是必須是原子操作,因此我們可以通過線程鎖來實現線程安全。
使用 Thread 對象的 Lock 和 Rlock 可以實現簡單的線程同步,這兩個對象都有 acquire 方法和 release 方法進行加鎖和解鎖。
lock=threading.RLock() # 創建一個鎖🔒
lock.acquire() # 加鎖
lock.release() # 解鎖
把每次只允許一個線程操作的數據,放到 acquire 和 release 方法之間進行操作。
全局解釋器鎖GIL
在非python環境中,單核情況下,同時只能有一一個任務執行。多核時可以支持多個線程同時執行。但是在python中,無論有多少核,同時只能執行一個線程。究其原因,這就是由於GIL的存在導致的。
GIL的全稱是Global Interpreter Lock(全局解釋器鎖),來源是python設計之初的考慮,爲了數據安全所做的決定。某個線程想要執行,必須先拿到GIL,我們可以把GIL看作是“通行證”,並且在一個python進程中,GIL只有一 一個。拿不到通行證的線程,就不允許進入CPU執行。GIL 只在cpython中才有,因爲cpython調用的是c語言的原生線程,所以他不能直接操作cpu,只能利用GIL保證同一-時間只能有- -個線程拿到數據。而在pypy和ipython中是沒有GIL的。
所以,在Python中,可以使用多線程,但不要指望能有效利用多核。不過,也不用過於擔心,Python雖然不能利用多線程實現多核任務,但可以通過多進程實現多核任務。多個Python進程有各自獨立的GIL鎖,互不影響
二、進程
使用多進程同樣可以完成多線程的工作,我們將之前的多線程程序改成多進程程序。
import multiprocessing
import time
def task():
for i in range(1,11):
time.sleep(1)
print(multiprocessing.current_process().name,__name__,end="")
print(":output >%d"%i)
if __name__=="__main__":
print(time.ctime())
p1=multiprocessing.Process(target=task)
p2=multiprocessing.Process(target=task)
p1.start()
p2.start()
p1.join()
p2.join()
print(time.ctime())
輸出:
Sat Mar 14 17:25:31 2020
Process-1 __mp_main__:output >1
Process-2 __mp_main__:output >1
Process-1 __mp_main__:output >2
Process-2 __mp_main__:output >2
Process-1 __mp_main__:output >3
Process-2 __mp_main__:output >3
Process-1 __mp_main__:output >4
Process-2 __mp_main__:output >4
Process-1 __mp_main__:output >5
Process-2 __mp_main__:output >5
Process-1 __mp_main__:output >6
Process-2 __mp_main__:output >6
Process-1 __mp_main__:output >7
Process-2 __mp_main__:output >7
Process-1 __mp_main__:output >8
Process-2 __mp_main__:output >8
Process-1 __mp_main__:output >9
Process-2 __mp_main__:output >9
Process-1 __mp_main__:output >10
Process-2 __mp_main__:output >10
Sat Mar 14 17:25:41 2020
進程池
通過進程池 pool 可以批量的創建大量的進程。
import multiprocessing
import os
import time
def task(i):
print("線程%d 執行: pid=%s"%(i,os.getpid()))
time.sleep(1)
return i
def back(n):
print("任務%d已完成"%n)
if __name__=="__main__":
mypool=multiprocessing.Pool(5) # 線程池大小
for i in range(1,21): # 20個線程任務
mypool.apply_async(func=task,args=(i,))
mypool.close()
mypool.join()
apply_async()
會等待線程池中有空閒時依次執行線程任務。其中,callback 參數是回調函數。
進程池中回調函數callback作用是:進程池中任何一個任務一旦處理完了,就立即告知主進程,主進程則調用一個函數去處理該結果,它可以用來接收進程任務的返回值,判斷其執行的結果。
輸出結果:
線程1 執行: pid=12372
線程2 執行: pid=11236
線程3 執行: pid=15144
線程4 執行: pid=8332
線程5 執行: pid=16732
線程6 執行: pid=12372
任務1已完成
線程7 執行: pid=11236
任務2已完成
線程8 執行: pid=15144
任務3已完成
線程9 執行: pid=8332
任務4已完成
線程10 執行: pid=16732
任務5已完成
線程11 執行: pid=12372
任務6已完成
線程12 執行: pid=11236
任務7已完成
線程13 執行: pid=15144
任務8已完成
線程14 執行: pid=8332
任務9已完成
線程15 執行: pid=16732
任務10已完成
線程16 執行: pid=12372
任務11已完成
線程17 執行: pid=11236
任務12已完成
線程18 執行: pid=15144
任務13已完成
線程19 執行: pid=8332
任務14已完成
線程20 執行: pid=16732
任務15已完成
任務16已完成
任務17已完成
任務18已完成
任務19已完成
任務20已完成
需要注意的是,在使用線程池時應注意將pool.close()
先於 pool.join()
調用。因爲 join() 會讓主進程阻塞等待子進程全部執行完之後再執行,close() 的作用是關閉pool,使其不在接受新的(主進程)任務。所以,兩個順序可不能顛倒了。