Python基礎(chǔ)教程之多線程編程詳解
進(jìn)程(process)指的是正在運(yùn)行的程序的實(shí)例,當(dāng)我們執(zhí)行某個(gè)程序時(shí),進(jìn)程就被操作系統(tǒng)創(chuàng)建了。而線程(thread)則包含于進(jìn)程之中,是操作系統(tǒng)能夠進(jìn)行運(yùn)算調(diào)度的最小單元,多個(gè)線程可以同處一個(gè)進(jìn)程中,且同時(shí)處理不同的任務(wù)。一條進(jìn)程中可以并發(fā)多個(gè)線程,而同一條線程將共享該進(jìn)程中的全部系統(tǒng)資源。
每個(gè)進(jìn)程都有自己獨(dú)立的地址空間、內(nèi)存和數(shù)據(jù)棧,因此進(jìn)程之間通訊不方便,所以需使用用進(jìn)程間通訊(InterProcess Communication, IPC)。而同一個(gè)進(jìn)程中的線程共享資源,因此線程間通訊非常方便,只需注意數(shù)據(jù)同步與互斥的問題。
1. 多線程基礎(chǔ)
線程是進(jìn)程內(nèi)的執(zhí)行單元,每個(gè)進(jìn)程都至少有一個(gè)線程。Python標(biāo)準(zhǔn)庫提供了thread模塊和threading模塊來支持線程編程。在Python3中,thread模塊已經(jīng)改名為_thread
,并在_thread
基礎(chǔ)上開發(fā)了更為強(qiáng)大的threading
模塊,因此我們可以使用threading.Thread
類創(chuàng)建線程對(duì)象,并通過start()
方法啟動(dòng)線程。
import threading def worker(): print("I am a thread.") t = threading.Thread(target=worker) t.start()
在上面的代碼中,我們創(chuàng)建了一個(gè)名為worker的函數(shù),它是線程的工作內(nèi)容。我們使用threading.Thread類創(chuàng)建了一個(gè)線程對(duì)象t,并將worker作為參數(shù)傳遞給了Thread構(gòu)造函數(shù)。然后,我們調(diào)用t.start()方法來啟動(dòng)線程。當(dāng)我們運(yùn)行這段代碼時(shí),會(huì)看到以下輸出:
I am a thread.
另外,我們也可以通過繼承Thread類并重寫run
方法來實(shí)現(xiàn)線程的創(chuàng)建.
import threading class MyThread(threading.Thread): def __init__(self, n): self.n = n super().__init__() # 調(diào)用父類函數(shù)初始化線程 def run(self): print('線程:', self.n) for i in range(1,3): t = MyThread(i) t.start()
果應(yīng)該輸出打印出:
線程1
線程2
2. 多線程同步
當(dāng)多個(gè)線程同時(shí)訪問共享資源時(shí),可能會(huì)發(fā)生競態(tài)條件。為避免這種情況,我們需要使用線程同步機(jī)制。Python提供了鎖(Lock)、信號(hào)量(Semaphore)、事件(Event)等機(jī)制來實(shí)現(xiàn)線程同步。
下面是一個(gè)使用鎖來確保線程同步的示例:
import threading lock = threading.Lock() count = 0 def worker(): global count lock.acquire() try: count += 1 finally: lock.release() threads = [] for i in range(10): t = threading.Thread(target=worker) threads.append(t) for t in threads: t.start() for t in threads: t.join() print(count)
在上面的代碼中,我們定義了一個(gè)全局變量count
和一個(gè)鎖對(duì)象lock
。worker
函數(shù)是每個(gè)線程的工作內(nèi)容,它通過lock
來確保count
的修改是原子性的。我們創(chuàng)建了10個(gè)線程并啟動(dòng)它們,等待所有線程執(zhí)行完畢后輸出count
的值。當(dāng)我們運(yùn)行這段代碼時(shí),會(huì)看到以下輸出:
10
如果把鎖去掉后,count
的值會(huì)是幾呢?動(dòng)手試試吧!會(huì)有意向不到的結(jié)果哦~
3. 線程池
線程池可以用來提高代碼的性能,在需要大量線程處理任務(wù)時(shí),可以使用線程池來減少線程的創(chuàng)建和銷毀次數(shù),提高線程的復(fù)用率。
Python
標(biāo)準(zhǔn)庫提供了concurrent.futures
模塊來支持線程池編程。我們可以使用ThreadPoolExecutor
類來創(chuàng)建和管理線程池。
下面是一個(gè)使用線程池來處理任務(wù)的示例:
from concurrent.futures import ThreadPoolExecutor def worker(num): return num * num executor = ThreadPoolExecutor(max_workers=4) results = [] for i in range(10): result = executor.submit(worker, i) results.append(result) for result in results: print(result.result())
在上面的代碼中,我們定義了一個(gè)worker
函數(shù)用來處理任務(wù)。我們創(chuàng)建了一個(gè)ThreadPoolExecutor
對(duì)象executor
,并將最大工作線程數(shù)設(shè)置為4
。然后,我們使用submit
方法提交10個(gè)任務(wù)給executor
,并將結(jié)果保存在results
列表中。最后,我們遍歷results
列表并輸出每個(gè)任務(wù)的結(jié)果。
看到這些個(gè)熟悉的單詞...恍惚間有種寫java代碼的感覺。
4. 多進(jìn)程編程
進(jìn)程是計(jì)算機(jī)中程序執(zhí)行的基本單位,而線程則是進(jìn)程中執(zhí)行代碼的單位。多進(jìn)程編程就是利用多個(gè)獨(dú)立運(yùn)行的進(jìn)程來完成任務(wù)。相比單進(jìn)程,多進(jìn)程有以下優(yōu)點(diǎn):
- 提高了程序的并發(fā)性能。
- 可以更好地利用多核CPU。
- 能夠處理更大的數(shù)據(jù)集。
在進(jìn)行多進(jìn)程編程時(shí),需要注意進(jìn)程之間的通信和同步問題。
5. Python實(shí)現(xiàn)多進(jìn)程編程
Python標(biāo)準(zhǔn)庫中的multiprocessing
模塊提供了實(shí)現(xiàn)多進(jìn)程編程的工具。其中常用的方法包括:
- Process:創(chuàng)建一個(gè)新進(jìn)程。
- Pool:創(chuàng)建一組進(jìn)程池。
- Queue:進(jìn)程之間的消息隊(duì)列。
下面是一個(gè)簡單的示例,展示如何使用multiprocessing
模塊創(chuàng)建子進(jìn)程:
import multiprocessing def worker(): """子進(jìn)程要執(zhí)行的任務(wù)""" print('子進(jìn)程正在運(yùn)行') if __name__ == '__main__': p = multiprocessing.Process(target=worker) p.start() print('主進(jìn)程已經(jīng)結(jié)束')
在這個(gè)例子中,我們創(chuàng)建一個(gè)新的進(jìn)程,并將其target
屬性指向worker
函數(shù)。然后使用start
方法啟動(dòng)該進(jìn)程。注意到在Windows系統(tǒng)中,需要將啟動(dòng)進(jìn)程的代碼放在if __name__ == '__main__':
語句內(nèi),以避免出現(xiàn)一些問題。
5.1 Python進(jìn)程池
import multiprocessing def worker(num): """子進(jìn)程要執(zhí)行的任務(wù)""" print(f'子進(jìn)程{num}正在運(yùn)行') if __name__ == '__main__': with multiprocessing.Pool(processes=4) as pool: results = pool.map(worker, range(4)) print(results)
在這個(gè)例子中,我們使用了進(jìn)程池來管理多個(gè)子進(jìn)程。首先創(chuàng)建一個(gè)Pool對(duì)象,其中processes參數(shù)指定了進(jìn)程池的大小。然后使用map方法提交任務(wù),該方法會(huì)自動(dòng)分配任務(wù)給空閑的子進(jìn)程并返回結(jié)果。最后打印輸出結(jié)果。
5.2 在進(jìn)程之間進(jìn)行通信
import multiprocessing def writer(q): """寫入數(shù)據(jù)""" for i in range(10): q.put(i) def reader(q): """讀取數(shù)據(jù)""" while True: item = q.get() if item is None: break print(item) if __name__ == '__main__': q = multiprocessing.Queue() # 啟動(dòng)子進(jìn)程 p1 = multiprocessing.Process(target=writer, args=(q,)) p2 = multiprocessing.Process(target=reader, args=(q,)) p1.start() p2.start() # 等待子進(jìn)程結(jié)束 p1.join() q.put(None) p2.join()
在這個(gè)例子中,我們創(chuàng)建了一個(gè)消息隊(duì)列,并通過Queue對(duì)象將其傳遞給兩個(gè)子進(jìn)程。一個(gè)子進(jìn)程負(fù)責(zé)將數(shù)據(jù)寫入隊(duì)列中,而另一個(gè)子進(jìn)程則從隊(duì)列中讀取數(shù)據(jù)并輸出到屏幕上。注意到在程序末尾我們向隊(duì)列中添加了None元素,以便通知讀取數(shù)據(jù)的子進(jìn)程結(jié)束循環(huán)。
6.多進(jìn)程計(jì)算圓周率
下面是使用Python多進(jìn)程和蒙特卡羅(Monte Carlo)方法估計(jì)圓周率的示例代碼:
import random from multiprocessing import Pool def estimate_pi(n): num_points_inside_circle = 0 for _ in range(n): x = random.uniform(-1, 1) y = random.uniform(-1, 1) if x**2 + y**2 <= 1: num_points_inside_circle += 1 return 4 * num_points_inside_circle / n if __name__ == '__main__': num_processes = 4 num_samples = int(1e8) with Pool(num_processes) as p: results = p.map(estimate_pi, [int(num_samples/num_processes)] * num_processes) print(sum(results)/num_processes)
該代碼使用random
模塊中的uniform
函數(shù)生成在[−1,1][-1, 1][−1,1]范圍內(nèi)均勻分布的隨機(jī)數(shù),并統(tǒng)計(jì)落入以原點(diǎn)為圓心,半徑為1的圓中的點(diǎn)數(shù)。最后,將所有子進(jìn)程的結(jié)果相加并除以進(jìn)程數(shù)以得到圓周率的估計(jì)值。我算出的結(jié)果是3.1416664800000005
, 你們呢?
其中,Pool(num_processes)
創(chuàng)建一個(gè)具有num_processes
個(gè)進(jìn)程的進(jìn)程池,p.map(estimate_pi, [int(num_samples/num_processes)] * num_processes)
在每個(gè)進(jìn)程上調(diào)用estimate_pi
函數(shù),并將輸入?yún)?shù)設(shè)為所需的樣本數(shù)量的四分之一,然后返回其結(jié)果。
7.總結(jié)
在Python多線程與進(jìn)程編程中,我們不僅要理解和掌握的關(guān)于threading
模塊和multiprocessing
模塊如何使用,更要掌握的是線程和進(jìn)程之間的區(qū)別和聯(lián)系以及線程通信、進(jìn)程通信的方式。
Python提供了豐富的多線程和多進(jìn)程編程工具,使我們能夠更有效地利用計(jì)算機(jī)的多核心處理能力。線程同步和線程池等同步原語可以幫助我們避免競爭條件,并提高程序性能。使用好多線程和多進(jìn)程,可以使Python編程變得更加高效和靈活。Python爬蟲應(yīng)用中,經(jīng)常會(huì)大量使用線程和進(jìn)程來提升抓取效率哦
以上就是Python基礎(chǔ)教程之多線程編程詳解的詳細(xì)內(nèi)容,更多關(guān)于Python多線程編程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
python利用tkinter實(shí)現(xiàn)屏保
這篇文章主要為大家詳細(xì)介紹了python利用tkinter實(shí)現(xiàn)屏保,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-07-07Python之Django自動(dòng)實(shí)現(xiàn)html代碼(下拉框,數(shù)據(jù)選擇)
這篇文章主要介紹了Python之Django自動(dòng)實(shí)現(xiàn)html代碼(下拉框,數(shù)據(jù)選擇),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過來看看吧2020-03-03Windows下實(shí)現(xiàn)pytorch環(huán)境搭建
這篇文章主要介紹了Windows下實(shí)現(xiàn)pytorch環(huán)境搭建,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2021-04-04基于Python的socket庫實(shí)現(xiàn)通信功能的示例代碼
本文主要給大家介紹了如何使用python的socket庫實(shí)現(xiàn)通信功能,這里簡單的給每個(gè)客戶端增加一個(gè)不重復(fù)的uid,客戶端之間可以根據(jù)這個(gè)uid選擇進(jìn)行廣播通信,感興趣的小伙伴快來看看吧2023-08-08Python中使用Inotify監(jiān)控文件實(shí)例
這篇文章主要介紹了Python中使用Inotify監(jiān)控文件實(shí)例,本文直接給出實(shí)現(xiàn)代碼,需要的朋友可以參考下2015-02-02Python K-means實(shí)現(xiàn)簡單圖像聚類的示例代碼
本文主要介紹了Python K-means實(shí)現(xiàn)簡單圖像聚類的示例代碼,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-10-10