Python中安全地使用多進(jìn)程和多線程進(jìn)行數(shù)據(jù)共享
1.前言
Python 中的并發(fā)與并行編程是為了提高程序的執(zhí)行效率,尤其是處理大規(guī)模計(jì)算任務(wù)和 I/O 密集型操作時(shí)。Python 提供了多線程 (Threading) 和多進(jìn)程 (Multiprocessing) 的方式來(lái)實(shí)現(xiàn)并發(fā)和并行處理。然而,由于 Python 的 GIL (Global Interpreter Lock) 存在,多線程并不能在 CPU 密集型任務(wù)中充分發(fā)揮多核優(yōu)勢(shì),但在 I/O 密集型任務(wù)中表現(xiàn)良好。而對(duì)于 CPU 密集型任務(wù),使用多進(jìn)程更為合適。
在并發(fā)編程中,有時(shí)多個(gè)線程或進(jìn)程需要訪問(wèn)共享的數(shù)據(jù),因此我們需要一些機(jī)制來(lái)確保數(shù)據(jù)的安全訪問(wèn)。本文將從多線程和多進(jìn)程兩個(gè)角度探討如何安全地實(shí)現(xiàn)數(shù)據(jù)共享。
2. 多線程中的數(shù)據(jù)共享
Python 中的多線程通過(guò) threading 模塊來(lái)實(shí)現(xiàn)。多個(gè)線程在同一進(jìn)程中運(yùn)行,天然地共享內(nèi)存空間,因此可以輕松地共享數(shù)據(jù)。然而,在多個(gè)線程訪問(wèn)共享數(shù)據(jù)時(shí),我們需要采取一些措施來(lái)防止數(shù)據(jù)競(jìng)爭(zhēng),避免線程之間的數(shù)據(jù)不一致問(wèn)題。

2.1 使用鎖 (Lock) 來(lái)保護(hù)共享數(shù)據(jù)
為了確保線程安全,通常會(huì)使用鎖 (Lock) 來(lái)保護(hù)共享資源。鎖的作用是保證在某一時(shí)刻,只有一個(gè)線程能夠訪問(wèn)共享資源。
下面是一個(gè)例子,演示如何在多線程中使用鎖來(lái)共享數(shù)據(jù)。
import threading
# 初始化共享數(shù)據(jù)
shared_data = 0
# 創(chuàng)建鎖對(duì)象
lock = threading.Lock()
# 線程函數(shù)
def increment():
global shared_data
for _ in range(1000000):
# 使用鎖來(lái)保護(hù)共享數(shù)據(jù)
with lock:
shared_data += 1
# 創(chuàng)建兩個(gè)線程
thread1 = threading.Thread(target=increment)
thread2 = threading.Thread(target=increment)
# 啟動(dòng)線程
thread1.start()
thread2.start()
# 等待線程完成
thread1.join()
thread2.join()
print(f"最終共享數(shù)據(jù)的值: {shared_data}")
2.2 解釋代碼
在上面的代碼中,我們創(chuàng)建了兩個(gè)線程來(lái)執(zhí)行 increment 函數(shù),這個(gè)函數(shù)會(huì)對(duì)全局變量 shared_data 進(jìn)行自增操作。如果沒(méi)有使用鎖,那么兩個(gè)線程可能會(huì)在同一時(shí)間訪問(wèn)和修改 shared_data,這會(huì)導(dǎo)致數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。
通過(guò) lock,我們可以確保在修改 shared_data 時(shí),只有一個(gè)線程可以進(jìn)入 with lock 代碼塊,從而避免了數(shù)據(jù)競(jìng)爭(zhēng),保證了線程安全。
3. 多進(jìn)程中的數(shù)據(jù)共享
Python 的多進(jìn)程支持通過(guò) multiprocessing 模塊來(lái)實(shí)現(xiàn)。多進(jìn)程與多線程的主要區(qū)別在于,每個(gè)進(jìn)程都有自己獨(dú)立的內(nèi)存空間,因此數(shù)據(jù)在進(jìn)程之間不能直接共享。為了在多進(jìn)程之間共享數(shù)據(jù),可以使用 multiprocessing 提供的共享機(jī)制,例如共享變量 (Value 和 Array) 和管理器 (Manager)。

3.1 使用 multiprocessing.Value 和 multiprocessing.Array
multiprocessing.Value 和 multiprocessing.Array 可以在進(jìn)程之間共享簡(jiǎn)單的數(shù)據(jù)類型和數(shù)組。
以下是一個(gè)例子,展示如何使用 multiprocessing.Value 來(lái)共享數(shù)據(jù)。
import multiprocessing
# 進(jìn)程函數(shù)
def increment(shared_value, lock):
for _ in range(1000000):
# 使用鎖來(lái)保護(hù)共享數(shù)據(jù)
with lock:
shared_value.value += 1
if __name__ == "__main__":
# 使用 Value 創(chuàng)建共享數(shù)據(jù),'i' 表示整數(shù)類型
shared_value = multiprocessing.Value('i', 0)
# 創(chuàng)建鎖對(duì)象
lock = multiprocessing.Lock()
# 創(chuàng)建兩個(gè)進(jìn)程
process1 = multiprocessing.Process(target=increment, args=(shared_value, lock))
process2 = multiprocessing.Process(target=increment, args=(shared_value, lock))
# 啟動(dòng)進(jìn)程
process1.start()
process2.start()
# 等待進(jìn)程完成
process1.join()
process2.join()
print(f"最終共享數(shù)據(jù)的值: {shared_value.value}")
3.2 解釋代碼
在這個(gè)例子中,shared_value 是一個(gè)通過(guò) multiprocessing.Value 創(chuàng)建的共享整數(shù)類型變量。與多線程類似,我們也需要使用鎖來(lái)保證在不同進(jìn)程中對(duì)共享變量的訪問(wèn)是安全的。
increment 函數(shù)每次自增 shared_value,使用 lock 來(lái)確保只有一個(gè)進(jìn)程能夠同時(shí)修改該值,避免數(shù)據(jù)競(jìng)爭(zhēng)問(wèn)題。
3.3 使用 multiprocessing.Manager
multiprocessing.Manager 是一種更靈活的進(jìn)程間共享數(shù)據(jù)的方式,可以用于共享更復(fù)雜的數(shù)據(jù)結(jié)構(gòu),例如列表和字典。
以下是一個(gè)使用 multiprocessing.Manager 來(lái)共享列表的例子:
import multiprocessing
# 進(jìn)程函數(shù)
def append_data(shared_list, lock):
for _ in range(5):
with lock:
shared_list.append(multiprocessing.current_process().name)
if __name__ == "__main__":
# 創(chuàng)建一個(gè)管理器對(duì)象
with multiprocessing.Manager() as manager:
shared_list = manager.list() # 創(chuàng)建共享列表
lock = multiprocessing.Lock()
# 創(chuàng)建多個(gè)進(jìn)程
processes = [multiprocessing.Process(target=append_data, args=(shared_list, lock)) for _ in range(4)]
# 啟動(dòng)進(jìn)程
for p in processes:
p.start()
# 等待進(jìn)程完成
for p in processes:
p.join()
print(f"最終共享列表的值: {list(shared_list)}")
3.4 解釋代碼
在這個(gè)例子中,我們使用 multiprocessing.Manager 來(lái)創(chuàng)建共享列表 shared_list,并在多個(gè)進(jìn)程中對(duì)該列表進(jìn)行修改。使用鎖 lock 來(lái)保護(hù) append 操作,以確保數(shù)據(jù)的安全性。
4. 線程和進(jìn)程的選擇
在 Python 中,選擇使用多線程還是多進(jìn)程主要取決于任務(wù)的類型。
- I/O 密集型任務(wù):例如網(wǎng)絡(luò)請(qǐng)求、文件讀寫(xiě)等,推薦使用多線程,因?yàn)檫@些操作會(huì)經(jīng)常等待外部資源,GIL 并不會(huì)對(duì) I/O 操作產(chǎn)生太多影響。
- CPU 密集型任務(wù):例如大規(guī)模計(jì)算和數(shù)學(xué)運(yùn)算,推薦使用多進(jìn)程,以繞過(guò) GIL 限制,充分利用多核 CPU 的計(jì)算能力。
5. 更高層次的并發(fā)模型 - 生產(chǎn)者消費(fèi)者模型
在多線程或多進(jìn)程中,我們通常會(huì)遇到生產(chǎn)者-消費(fèi)者的場(chǎng)景:一個(gè)線程或進(jìn)程生產(chǎn)數(shù)據(jù),另一個(gè)線程或進(jìn)程消費(fèi)數(shù)據(jù)。在 Python 中,我們可以使用 queue.Queue 和 multiprocessing.Queue 來(lái)實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模型。
5.1 使用 queue.Queue 實(shí)現(xiàn)多線程的生產(chǎn)者消費(fèi)者模型
以下是一個(gè)多線程的例子,使用 queue.Queue 來(lái)實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模型。
import threading
import queue
import time
# 創(chuàng)建一個(gè)隊(duì)列
data_queue = queue.Queue()
# 生產(chǎn)者函數(shù)
def producer():
for i in range(5):
time.sleep(1) # 模擬生產(chǎn)時(shí)間
item = f"item_{i}"
data_queue.put(item)
print(f"生產(chǎn)者生產(chǎn)了: {item}")
# 消費(fèi)者函數(shù)
def consumer():
while True:
item = data_queue.get()
if item is None:
break
print(f"消費(fèi)者消費(fèi)了: {item}")
data_queue.task_done()
# 創(chuàng)建生產(chǎn)者線程和消費(fèi)者線程
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)
# 啟動(dòng)線程
producer_thread.start()
consumer_thread.start()
# 等待生產(chǎn)者線程完成
producer_thread.join()
# 向隊(duì)列中放置 None,表示消費(fèi)者可以退出
data_queue.put(None)
# 等待消費(fèi)者線程完成
consumer_thread.join()
5.2 使用 multiprocessing.Queue 實(shí)現(xiàn)多進(jìn)程的生產(chǎn)者消費(fèi)者模型
以下是一個(gè)多進(jìn)程的例子,使用 multiprocessing.Queue 來(lái)實(shí)現(xiàn)生產(chǎn)者消費(fèi)者模型。
import multiprocessing
import time
# 生產(chǎn)者函數(shù)
def producer(queue):
for i in range(5):
time.sleep(1) # 模擬生產(chǎn)時(shí)間
item = f"item_{i}"
queue.put(item)
print(f"生產(chǎn)者生產(chǎn)了: {item}")
# 消費(fèi)者函數(shù)
def consumer(queue):
while True:
item = queue.get()
if item is None:
break
print(f"消費(fèi)者消費(fèi)了: {item}")
if __name__ == "__main__":
# 創(chuàng)建共享隊(duì)列
queue = multiprocessing.Queue()
# 創(chuàng)建生產(chǎn)者進(jìn)程和消費(fèi)者進(jìn)程
producer_process = multiprocessing.Process(target=producer, args=(queue,))
consumer_process = multiprocessing.Process(target=consumer, args=(queue,))
# 啟動(dòng)進(jìn)程
producer_process.start()
consumer_process.start()
# 等待生產(chǎn)者進(jìn)程完成
producer_process.join()
# 向隊(duì)列中放置 None,表示消費(fèi)者可以退出
queue.put(None)
# 等待消費(fèi)者進(jìn)程完成
consumer_process.join()

6. 總結(jié)共享數(shù)據(jù)的常用方式
在 Python 中,使用多線程和多進(jìn)程進(jìn)行數(shù)據(jù)共享時(shí),必須考慮線程安全和進(jìn)程間通信的問(wèn)題??偨Y(jié)一下常用的方式:
多線程數(shù)據(jù)共享:
- 使用
threading.Lock來(lái)確保對(duì)共享數(shù)據(jù)的安全訪問(wèn)。 - 使用
queue.Queue來(lái)實(shí)現(xiàn)線程安全的生產(chǎn)者消費(fèi)者模型。
- 使用
多進(jìn)程數(shù)據(jù)共享:
- 使用
multiprocessing.Value和multiprocessing.Array來(lái)共享簡(jiǎn)單數(shù)據(jù)類型。 - 使用
multiprocessing.Manager來(lái)共享復(fù)雜的數(shù)據(jù)結(jié)構(gòu)(如列表和字典)。 - 使用
multiprocessing.Queue來(lái)實(shí)現(xiàn)進(jìn)程間的生產(chǎn)者消費(fèi)者模型。
- 使用
每一種方法都有其適用的場(chǎng)景和局限性。在實(shí)際開(kāi)發(fā)中,需根據(jù)任務(wù)的性質(zhì)和數(shù)據(jù)共享的復(fù)雜度選擇合適的方式。
以上就是 Python中安全地使用多進(jìn)程和多線程進(jìn)行數(shù)據(jù)共享的詳細(xì)內(nèi)容,更多關(guān)于 Python數(shù)據(jù)共享的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
教你使用Sublime text3搭建Python開(kāi)發(fā)環(huán)境及常用插件安裝另分享Sublime text3最新激活注冊(cè)碼
這篇文章主要介紹了使用Sublime text 3搭建Python開(kāi)發(fā)環(huán)境及常用插件安裝,并提供了最新Sublime text 3激活注冊(cè)碼需要的朋友可以參考下2020-11-11
Python操作MySQL數(shù)據(jù)庫(kù)的基本方法(查詢與更新)
在工作中我們需要經(jīng)常對(duì)數(shù)據(jù)庫(kù)進(jìn)行操作,比如 Oracle、MySQL、SQL Sever等,這篇文章主要給大家介紹了關(guān)于Python操作MySQL數(shù)據(jù)庫(kù)的基本方法包括了數(shù)據(jù)查詢與數(shù)據(jù)更新(新增、刪除、修改),需要的朋友可以參考下2023-09-09
詳解Python數(shù)據(jù)可視化編程 - 詞云生成并保存(jieba+WordCloud)
這篇文章主要介紹了Python數(shù)據(jù)可視化編程 - 詞云生成并保存(jieba+WordCloud),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03
Python代碼顯得Pythonic(區(qū)別于其他語(yǔ)言的寫(xiě)法)
這篇文章主要介紹了Python代碼顯得Pythonic(區(qū)別于其他語(yǔ)言的寫(xiě)法),對(duì)于字符串連接,相比于簡(jiǎn)單的+,更pythonic的做法是盡量使用%操作符或者format函數(shù)格式化字符串,感興趣的小伙伴和小編一起進(jìn)入文章了解更詳細(xì)相關(guān)知識(shí)內(nèi)容吧2022-02-02
Python利用3D引擎寫(xiě)一個(gè)Pong游戲
之前,我們嘗試過(guò)用pygame做了一個(gè)2D的Pong游戲。本文將利用強(qiáng)大的3D引擎Ursina制作一個(gè)3D版的Pong游戲。文中的示例代碼講解詳細(xì),感興趣的可以了解一下2023-01-01
Python反爬機(jī)制-驗(yàn)證碼功能的具體實(shí)現(xiàn)過(guò)程
Tesseract-OCR是一個(gè)免費(fèi)、開(kāi)源的OCR引擎,通過(guò)該引擎可以識(shí)別圖片中的驗(yàn)證碼,這篇文章主要介紹了Python反爬機(jī)制-驗(yàn)證碼的示例代碼,需要的朋友可以參考下2022-02-02
Using Django with GAE Python 后臺(tái)抓取多個(gè)網(wǎng)站的頁(yè)面全文
這篇文章主要介紹了Using Django with GAE Python 后臺(tái)抓取多個(gè)網(wǎng)站的頁(yè)面全文,需要的朋友可以參考下2016-02-02
python匹配兩個(gè)短語(yǔ)之間的字符實(shí)例
今天小編就為大家分享一篇python匹配兩個(gè)短語(yǔ)之間的字符實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2018-12-12

