目錄 1 基礎(chǔ)知識 1.1 線程 1.2 進(jìn)程 1.3 兩者的區(qū)別 2 Python 多進(jìn)程 2.1 創(chuàng)建多進(jìn)程 方法1:直接使用Process 方法2:繼承Process來自定義進(jìn)程類,重寫run方法 2.2 多進(jìn)程通信 2.3 進(jìn)程池 3 Python 多線程 3.1 GIL 3.2 創(chuàng)建多線程 方法1:直接使用threading.Thread() 方法2:繼承threading.Thread來自定義線程類,重寫run方法 3.3 線程合并 3.4 線程同步與互斥鎖 3.5 可重入鎖(遞歸鎖) 3.6 守護(hù)線程 3.7 定時(shí)器 4 選擇多線程還是多進(jìn)程? python的多線程比較雞肋,優(yōu)先使用多進(jìn)程
1 基礎(chǔ)知識
現(xiàn)在的 PC 都是多核的,使用多線程能充分利用 CPU 來提供程序的執(zhí)行效率。
1.1 線程
線程是一個(gè)基本的 CPU 執(zhí)行單元。它必須依托于進(jìn)程存活。一個(gè)線程是一個(gè)execution context(執(zhí)行上下文),即一個(gè) CPU 執(zhí)行時(shí)所需要的一串指令。
1.2 進(jìn)程
進(jìn)程是指一個(gè)程序在給定數(shù)據(jù)集合上的一次執(zhí)行過程,是系統(tǒng)進(jìn)行資源分配和運(yùn)行調(diào)用的獨(dú)立單位。可以簡單地理解為操作系統(tǒng)中正在執(zhí)行的程序。也就說,每個(gè)應(yīng)用程序都有一個(gè)自己的進(jìn)程。
每一個(gè)進(jìn)程啟動(dòng)時(shí)都會(huì)最先產(chǎn)生一個(gè)線程,即主線程。然后主線程會(huì)再創(chuàng)建其他的子線程
1.3 兩者的區(qū)別
線程必須在某個(gè)進(jìn)行中執(zhí)行。
一個(gè)進(jìn)程可包含多個(gè)線程,其中有且只有一個(gè)主線程。
多線程共享同個(gè)地址空間、打開的文件以及其他資源。
多進(jìn)程共享物理內(nèi)存、磁盤、打印機(jī)以及其他資源。
2 Python 多進(jìn)程
2.1 創(chuàng)建多進(jìn)程
Python 要進(jìn)行多進(jìn)程操作,需要用到muiltprocessing庫,其中的Process類跟threading模塊的Thread類很相似。所以直接看代碼熟悉多進(jìn)程。
方法1:直接使用Process
代碼如下:
from multiprocessing import Process
def show(name):
print("Process name is " + name)
if __name__ == "__main__":
proc = Process(target=show, args=('subprocess',))
proc.start()
proc.join()
方法2:繼承Process來自定義進(jìn)程類,重寫run方法
代碼如下:
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self, name):
super(MyProcess, self).__init__()
self.name = name
def run(self):
print('process name :' + str(self.name))
time.sleep(1)
if __name__ == '__main__':
for i in range(3):
p = MyProcess(i)
p.start()
for i in range(3):
p.join()
2.2 多進(jìn)程通信
進(jìn)程之間不共享數(shù)據(jù)的。如果進(jìn)程之間需要進(jìn)行通信,則要用到Queue模塊或者Pipi模塊來實(shí)現(xiàn)。
Queue
Queue 是多進(jìn)程安全的隊(duì)列,可以實(shí)現(xiàn)多進(jìn)程之間的數(shù)據(jù)傳遞。它主要有兩個(gè)函數(shù),put和get。
put() 用以插入數(shù)據(jù)到隊(duì)列中,put 還有兩個(gè)可選參數(shù):blocked 和 timeout。如果 blocked 為 True(默認(rèn)值),并且 timeout 為正值,該方法會(huì)阻塞 timeout 指定的時(shí)間,直到該隊(duì)列有剩余的空間。如果超時(shí),會(huì)拋出 Queue.Full 異常。如果 blocked 為 False,但該 Queue 已滿,會(huì)立即拋出 Queue.Full 異常。
get()可以從隊(duì)列讀取并且刪除一個(gè)元素。同樣,get 有兩個(gè)可選參數(shù):blocked 和 timeout。如果 blocked 為 True(默認(rèn)值),并且 timeout 為正值,那么在等待時(shí)間內(nèi)沒有取到任何元素,會(huì)拋出 Queue.Empty 異常。如果blocked 為 False,有兩種情況存在,如果 Queue 有一個(gè)值可用,則立即返回該值,否則,如果隊(duì)列為空,則立即拋出 Queue.Empty 異常。
具體用法如下:
from multiprocessing import Process, Queue
def put(queue):
queue.put('Queue 用法')
if __name__ == '__main__':
queue = Queue()
pro = Process(target=put, args=(queue,))
pro.start()
print(queue.get())
pro.join()
Pipe
Pipe的本質(zhì)是進(jìn)程之間的用管道數(shù)據(jù)傳遞,而不是數(shù)據(jù)共享,這和socket有點(diǎn)像。pipe() 返回兩個(gè)連接對象分別表示管道的兩端,每端都有send() 和recv()函數(shù)。
如果兩個(gè)進(jìn)程試圖在同一時(shí)間的同一端進(jìn)行讀取和寫入那么,這可能會(huì)損壞管道中的數(shù)據(jù)。
具體用法如下:
from multiprocessing import Process, Pipe
def show(conn):
conn.send('Pipe 用法')
conn.close()
if __name__ == '__main__':
parent_conn, child_conn = Pipe()
pro = Process(target=show, args=(child_conn,))
pro.start()
print(parent_conn.recv())
pro.join()
2.3 進(jìn)程池
創(chuàng)建多個(gè)進(jìn)程,我們不用傻傻地一個(gè)個(gè)去創(chuàng)建。我們可以使用Pool模塊來搞定。
Pool 常用的方法如下:
具體用法見示例代碼:
from multiprocessing import Pool
def show(num):
print('num : ' + str(num))
if __name__=="__main__":
pool = Pool(processes = 3)
for i in xrange(6):
# 維持執(zhí)行的進(jìn)程總數(shù)為processes,當(dāng)一個(gè)進(jìn)程執(zhí)行完畢后會(huì)添加新的進(jìn)程進(jìn)去
pool.apply_async(show, args=(i, ))
print('====== apply_async ======')
pool.close()
#調(diào)用join之前,先調(diào)用close函數(shù),否則會(huì)出錯(cuò)。執(zhí)行完close后不會(huì)有新的進(jìn)程加入到pool,join函數(shù)等待所有子進(jìn)程結(jié)束
pool.join()
3 Python 多線程
3.1 GIL
其他語言,CPU 是多核時(shí)是支持多個(gè)線程同時(shí)執(zhí)行。但在 Python 中,無論是單核還是多核,同時(shí)只能由一個(gè)線程在執(zhí)行。其根源是 GIL 的存在。
GIL 的全稱是 Global Interpreter Lock(全局解釋器鎖),來源是 Python 設(shè)計(jì)之初的考慮,為了數(shù)據(jù)安全所做的決定。某個(gè)線程想要執(zhí)行,必須先拿到 GIL,我們可以把 GIL 看作是“通行證”,并且在一個(gè) Python 進(jìn)程中,GIL 只有一個(gè)。拿不到通行證的線程,就不允許進(jìn)入 CPU 執(zhí)行。
而目前 Python 的解釋器有多種,例如:
CPython
:CPython 是用C語言實(shí)現(xiàn)的 Python 解釋器。 作為官方實(shí)現(xiàn),它是最廣泛使用的 Python 解釋器。
PyPy
:PyPy 是用RPython實(shí)現(xiàn)的解釋器。RPython 是 Python 的子集, 具有靜態(tài)類型。這個(gè)解釋器的特點(diǎn)是即時(shí)編譯,支持多重后端(C, CLI, JVM)。PyPy 旨在提高性能,同時(shí)保持最大兼容性(參考 CPython 的實(shí)現(xiàn))。J
ython
:Jython 是一個(gè)將 Python 代碼編譯成 Java 字節(jié)碼的實(shí)現(xiàn),運(yùn)行在JVM (Java Virtual Machine) 上。另外,它可以像是用 Python 模塊一樣,導(dǎo)入 并使用任何Java類。IronPython:IronPython 是一個(gè)針對 .NET 框架的 Python 實(shí)現(xiàn)。它 可以用 Python 和 .NET framewor k的庫,也能將 Python 代碼暴露給 .NET 框架中的其他語言。
GIL 只在 CPython 中才有,而在 PyPy 和 Jython 中是沒有 GIL 的。
每次釋放 GIL鎖,線程進(jìn)行鎖競爭、切換線程,會(huì)消耗資源。這就導(dǎo)致打印線程執(zhí)行時(shí)長,會(huì)發(fā)現(xiàn)耗時(shí)更長的原因。
3.2 創(chuàng)建多線程
Python提供兩個(gè)模塊進(jìn)行多線程的操作,分別是thread和threading,
前者是比較低級的模塊,用于更底層的操作,一般應(yīng)用級別的開發(fā)不常用。
方法1:直接使用threading.Thread()
import threading
# 這個(gè)函數(shù)名可隨便定義
def run(n):
print("current task:", n)
if __name__ == "__main__":
t1 = threading.Thread(target=run, args=("thread 1",))
t2 = threading.Thread(target=run, args=("thread 2",))
t1.start()
t2.start()
方法2:繼承threading.Thread來自定義線程類,重寫run方法
import threading
class MyThread(threading.Thread):
def __init__(self, n):
super(MyThread, self).__init__() # 重構(gòu)run函數(shù)必須要寫
self.n = n
def run(self):
print("current task:", n)
if __name__ == "__main__":
t1 = MyThread("thread 1")
t2 = MyThread("thread 2")
t1.start()
t2.start()
3.3 線程合并
Join函數(shù)執(zhí)行順序是逐個(gè)執(zhí)行每個(gè)線程,執(zhí)行完畢后繼續(xù)往下執(zhí)行。主線程結(jié)束后,子線程還在運(yùn)行,join函數(shù)使得主線程等到子線程結(jié)束時(shí)才退出。
import threading
def count(n):
while n > 0:
n -= 1
if __name__ == "__main__":
t1 = threading.Thread(target=count, args=("100000",))
t2 = threading.Thread(target=count, args=("100000",))
t1.start()
t2.start()
# 將 t1 和 t2 加入到主線程中
t1.join()
t2.join()
3.4 線程同步與互斥鎖
線程之間數(shù)據(jù)共享的。當(dāng)多個(gè)線程對某一個(gè)共享數(shù)據(jù)進(jìn)行操作時(shí),就需要考慮到線程安全問題。threading模塊中定義了Lock 類,提供了互斥鎖的功能來保證多線程情況下數(shù)據(jù)的正確性。
用法的基本步驟:
#創(chuàng)建鎖
mutex = threading.Lock()
#鎖定
mutex.acquire([timeout])
#釋放
mutex.release()
其中,鎖定方法acquire可以有一個(gè)超時(shí)時(shí)間的可選參數(shù)timeout。如果設(shè)定了timeout,則在超時(shí)后通過返回值可以判斷是否得到了鎖,從而可以進(jìn)行一些其他的處理。
具體用法見示例代碼:
import threading
import time
num = 0
mutex = threading.Lock()
class MyThread(threading.Thread):
def run(self):
global num
time.sleep(1)
if mutex.acquire(1):
num = num + 1
msg = self.name + ': num value is ' + str(num)
print(msg)
mutex.release()
if __name__ == '__main__':
for i in range(5):
t = MyThread()
t.start()
3.5 可重入鎖(遞歸鎖)
為了滿足在同一線程中多次請求同一資源的需求,Python 提供了可重入鎖(RLock)。
RLock內(nèi)部維護(hù)著一個(gè)Lock和一個(gè)counter變量,counter 記錄了 acquire 的次數(shù),從而使得資源可以被多次 require。直到一個(gè)線程所有的 acquire 都被 release,其他的線程才能獲得資源。
具體用法如下:
#創(chuàng)建 RLock
mutex = threading.RLock()
class MyThread(threading.Thread):
def run(self):
if mutex.acquire(1):
print("thread " + self.name + " get mutex")
time.sleep(1)
mutex.acquire()
mutex.release()
mutex.release()
3.6 守護(hù)線程
如果希望主線程執(zhí)行完畢之后,不管子線程是否執(zhí)行完畢都隨著主線程一起結(jié)束。我們可以使用setDaemon(bool)函數(shù),它跟join函數(shù)是相反的。它的作用是設(shè)置子線程是否隨主線程一起結(jié)束,必須在start() 之前調(diào)用,默認(rèn)為False。
3.7 定時(shí)器
如果需要規(guī)定函數(shù)在多少秒后執(zhí)行某個(gè)操作,需要用到Timer類。具體用法如下:
from threading import Timer
def show():
print("Pyhton")
# 指定一秒鐘之后執(zhí)行 show 函數(shù)
t = Timer(1, hello)
t.start()
4 選擇多線程還是多進(jìn)程?
在這個(gè)問題上,首先要看下你的程序是屬于哪種類型的。一般分為兩種 CPU 密集型 和 I/O 密集型。
CPU 密集型:程序比較偏重于計(jì)算,需要經(jīng)常使用 CPU 來運(yùn)算。例如科學(xué)計(jì)算的程序,機(jī)器學(xué)習(xí)的程序等。
I/O 密集型:顧名思義就是程序需要頻繁進(jìn)行輸入輸出操作。爬蟲程序就是典型的 I/O 密集型程序。
如果程序是屬于 CPU 密集型,建議使用多進(jìn)程。而多線程就更適合應(yīng)用于 I/O 密集型程序。
以上就是分析詳解python多線程與多進(jìn)程區(qū)別的詳細(xì)內(nèi)容,更多關(guān)于python多線程與多進(jìn)程區(qū)別的資料請關(guān)注腳本之家其它相關(guān)文章!
您可能感興趣的文章: 手把手帶你了解python多進(jìn)程,多線程 Python多進(jìn)程共享numpy 數(shù)組的方法 總結(jié)python多進(jìn)程multiprocessing的相關(guān)知識 Python多線程與多進(jìn)程相關(guān)知識總結(jié) python實(shí)現(xiàn)多進(jìn)程并發(fā)控制Semaphore與互斥鎖LOCK python 多進(jìn)程和多線程使用詳解 python 實(shí)現(xiàn)多進(jìn)程日志輪轉(zhuǎn)ConcurrentLogHandler Python多進(jìn)程與多線程的使用場景詳解 python多進(jìn)程執(zhí)行方法apply_async使用說明 Python 多進(jìn)程原理及實(shí)現(xiàn) python多線程和多進(jìn)程關(guān)系詳解 Python多進(jìn)程的使用詳情