python中多線程與多進程中的數據共用問題

来源:https://www.cnblogs.com/yangfantuan/archive/2020/03/20/12535436.html
-Advertisement-
Play Games

之前在寫多線程與多進程的時候,因為一般情況下都是各自完成各自的任務,各個子線程或者各個子進程之前並沒有太多的聯繫,如果需要通信的話我會使用隊列或者資料庫來完成,但是最近我在寫一些多線程與多進程的代碼時,發現如果它們需要用到共用變數的話,需要有一些註意的地方 多線程之間的共用數據 標準數據類型線上程間 ...


之前在寫多線程與多進程的時候,因為一般情況下都是各自完成各自的任務,各個子線程或者各個子進程之前並沒有太多的聯繫,如果需要通信的話我會使用隊列或者資料庫來完成,但是最近我在寫一些多線程與多進程的代碼時,發現如果它們需要用到共用變數的話,需要有一些註意的地方

多線程之間的共用數據

標準數據類型線上程間共用

看以下代碼

#coding:utf-8
import threading

def test(name,data):
    print("in thread {} name is {}".format(threading.current_thread(),name))
    print("data is {} id(data) is {}".format(data,id(data)))


if __name__ == '__main__':
    d = 5
    name = "楊彥星"
    for i in range(5):
        th = threading.Thread(target=test,args=(name,d))
        th.start()

這裡我創建一個全局的int變數d,它的值是5,當我在5個線程中調用test函數時,將d作為參數傳進去,那麼這5個線程所擁有的是同一個d嗎?我在test函數中通過id(data) 來列印一下它們的ID,得到瞭如下的結果

in thread <Thread(Thread-1, started 6624)> name is 楊彥星
data is 5 id(data) is 1763791776
in thread <Thread(Thread-2, started 8108)> name is 楊彥星
data is 5 id(data) is 1763791776
in thread <Thread(Thread-3, started 3356)> name is 楊彥星
data is 5 id(data) is 1763791776
in thread <Thread(Thread-4, started 13728)> name is 楊彥星
data is 5 id(data) is 1763791776
in thread <Thread(Thread-5, started 3712)> name is 楊彥星
data is 5 id(data) is 1763791776

從結果中可以看到,在5個子線程中,data的id都是1763791776,說明在主線程中創建了變數d,在子線程中是可以共用的,在子線程中對共用元素的改變是會影響到其它線程的,所以如果要對共用變數進行修改時,也就是線程不安全的,需要加鎖。

自定義類型對象線上程間共用

如果我們要自定義一個類呢,將一個對象作為變數在子線程中傳遞呢?會是什麼效果呢?

#coding:utf-8
import threading

class Data:
    def __init__(self,data=None):
        self.data = data

    def get(self):
        return self.data

    def set(self,data):
        self.data = data

def test(name,data):
    print("in thread {} name is {}".format(threading.current_thread(),name))
    print("data is {} id(data) is {}".format(data.get(),id(data)))


if __name__ == '__main__':
    d = Data(10)
    name = "楊彥星"
    print("in main thread id(data) is {}".format(id(d)))
    for i in range(5):
        th = threading.Thread(target=test,args=(name,d))
        th.start()

這裡我定義一個簡單的類,在主線程初始化了一個該類型的對象d,然後將它作為參數傳給子線程,主線程和子線程分別列印了這個對象的id,我們來看一下結果

in main thread id(data) is 2849240813864
in thread <Thread(Thread-1, started 11648)> name is 楊彥星
data is 10 id(data) is 2849240813864
in thread <Thread(Thread-2, started 11016)> name is 楊彥星
data is 10 id(data) is 2849240813864
in thread <Thread(Thread-3, started 10416)> name is 楊彥星
data is 10 id(data) is 2849240813864
in thread <Thread(Thread-4, started 8668)> name is 楊彥星
data is 10 id(data) is 2849240813864
in thread <Thread(Thread-5, started 4420)> name is 楊彥星
data is 10 id(data) is 2849240813864

我們看到,在主線程和子線程中,這個對象的id是一樣的,說明它們用的是同一個對象。

無論是標準數據類型還是複雜的自定義數據類型,它們在多線程之間是共用同一個的,但是在多進程中是這樣的嗎?

多進程之間的共用數據

標準數據類型在進程間共用

還是上面的代碼,我們先來看一下int類型的變數的子進程間的共用

#coding:utf-8
import threading
import multiprocessing

def test(name,data):
    print("in thread {} name is {}".format(threading.current_thread(),name))
    print("data is {} id(data) is {}".format(data,id(data)))


if __name__ == '__main__':
    d = 10
    name = "楊彥星"
    print("in main thread id(data) is {}".format(id(d)))
    for i in range(5):
        pro = multiprocessing.Process(target=test,args=(name,d))
        pro.start()

得到的結果是

in main thread id(data) is 1763791936
in thread <_MainThread(MainThread, started 9364)> name is 楊彥星
data is 10 id(data) is 1763791936
in thread <_MainThread(MainThread, started 9464)> name is 楊彥星
data is 10 id(data) is 1763791936
in thread <_MainThread(MainThread, started 3964)> name is 楊彥星
data is 10 id(data) is 1763791936
in thread <_MainThread(MainThread, started 10480)> name is 楊彥星
data is 10 id(data) is 1763791936
in thread <_MainThread(MainThread, started 13608)> name is 楊彥星
data is 10 id(data) is 1763791936

可以看到它們的id是一樣的,說明用的是同一個變數,但是當我嘗試把d由int變為了string時,發現它們又不一樣了……

if __name__ == '__main__':
    d = 'yangyanxing'
    name = "楊彥星"
    print("in main thread id(data) is {}".format(id(d)))
    for i in range(5):
        pro = multiprocessing.Process(target=test,args=(name,d))
        pro.start()

此時得到的結果是

in main thread id(data) is 2629633397040
in thread <_MainThread(MainThread, started 9848)> name is 楊彥星
data is yangyanxing id(data) is 1390942032880
in thread <_MainThread(MainThread, started 988)> name is 楊彥星
data is yangyanxing id(data) is 2198251377648
in thread <_MainThread(MainThread, started 3728)> name is 楊彥星
data is yangyanxing id(data) is 2708672287728
in thread <_MainThread(MainThread, started 5288)> name is 楊彥星
data is yangyanxing id(data) is 2376058999792
in thread <_MainThread(MainThread, started 12508)> name is 楊彥星
data is yangyanxing id(data) is 2261044040688

於是我又嘗試了list、Tuple、dict,結果它們都是不一樣的,我又回過頭來試著在多線程中使用列表元組和字典,結果在多線程中它們的id還是一樣的。

這裡有一個有趣的問題,如果是int類型,當值小於等於256時,它們在多進程間的id是相同的,如果大於256,則它們的id就會不同了,這個我沒有查看原因。

自定義類型對象在進程間共用

#coding:utf-8
import threading
import multiprocessing

class Data:
    def __init__(self,data=None):
        self.data = data

    def get(self):
        return self.data

    def set(self,data):
        self.data = data

def test(name,data):
    print("in thread {} name is {}".format(threading.current_thread(),name))
    print("data is {} id(data) is {}".format(data.get(),id(data)))


if __name__ == '__main__':
    d = Data(10)
    name = "楊彥星"
    print("in main thread id(data) is {}".format(id(d)))
    for i in range(5):
        pro = multiprocessing.Process(target=test,args=(name,d))
        pro.start()

得到的結果是

in main thread id(data) is 1927286591728
in thread <_MainThread(MainThread, started 2408)> name is 楊彥星
data is 10 id(data) is 1561177927752
in thread <_MainThread(MainThread, started 5728)> name is 楊彥星
data is 10 id(data) is 2235260514376
in thread <_MainThread(MainThread, started 1476)> name is 楊彥星
data is 10 id(data) is 2350586073040
in thread <_MainThread(MainThread, started 996)> name is 楊彥星
data is 10 id(data) is 2125002248088
in thread <_MainThread(MainThread, started 10740)> name is 楊彥星
data is 10 id(data) is 1512231669656

可以看到它們的id是不同的,也就是不同的對象。

在多進程間如何共用數據

我們看到,數據在多進程間是不共用的(小於256的int類型除外),但是我們又想在主進程和子進程間共用一個數據對象時該如何操作呢?

在看這個問題之前,我們先將之前的多線程代碼做下修改

#coding:utf-8
import threading
import multiprocessing

class Data:
    def __init__(self,data=None):
        self.data = data

    def get(self):
        return self.data

    def set(self,data):
        self.data = data

def test(name,data,lock):
    lock.acquire()
    print("in thread {} name is {}".format(threading.current_thread(),name))
    print("data is {} id(data) is {}".format(data,id(data)))
    data.set(data.get()+1)
    lock.release()


if __name__ == '__main__':
    d = Data(0)
    thlist = []
    name = "yang"
    lock = threading.Lock()
    for i in range(5):
        th = threading.Thread(target=test,args=(name,d,lock))
        th.start()
        thlist.append(th)
    for i in thlist:
        i.join()
    print(d.get())

我們這個代碼的目的是這樣,使用自定義的Data類型對象,當經過5個子線程操作以後,每個子線程對其data值進行加1操作,最後在主線程列印對象的data值。
該輸出結果如下

in thread <Thread(Thread-1, started 3296)> name is yang
data is <__main__.Data object at 0x000001A451139198> id(data) is 1805246501272
in thread <Thread(Thread-2, started 9436)> name is yang
data is <__main__.Data object at 0x000001A451139198> id(data) is 1805246501272
in thread <Thread(Thread-3, started 760)> name is yang
data is <__main__.Data object at 0x000001A451139198> id(data) is 1805246501272
in thread <Thread(Thread-4, started 1952)> name is yang
data is <__main__.Data object at 0x000001A451139198> id(data) is 1805246501272
in thread <Thread(Thread-5, started 5988)> name is yang
data is <__main__.Data object at 0x000001A451139198> id(data) is 1805246501272
5

可以看到在主線程最後列印出來了5,符合我們的預期,但是如果放到多進程中呢?因為多進程下,每個子進程所持有的對象是不同的,所以每個子進程操作的是各自的Data對象,對於主進程的Data對象應該是沒有影響的,我們來看下它的結果

#coding:utf-8
import threading
import multiprocessing

class Data:
    def __init__(self,data=None):
        self.data = data

    def get(self):
        return self.data

    def set(self,data):
        self.data = data

def test(name,data,lock):
    lock.acquire()
    print("in thread {} name is {}".format(threading.current_thread(),name))
    print("data is {} id(data) is {}".format(data,id(data)))
    data.set(data.get()+1)
    lock.release()


if __name__ == '__main__':
    d = Data(0)
    thlist = []
    name = "yang"
    lock = multiprocessing.Lock()
    for i in range(5):
        th = multiprocessing.Process(target=test,args=(name,d,lock))
        th.start()
        thlist.append(th)
    for i in thlist:
        i.join()
    print(d.get())

它的輸出結果是:

in thread <_MainThread(MainThread, started 7604)> name is yang
data is <__mp_main__.Data object at 0x000001D110130EB8> id(data) is 1997429477048
in thread <_MainThread(MainThread, started 12108)> name is yang
data is <__mp_main__.Data object at 0x000002C4E88E0E80> id(data) is 3044738469504
in thread <_MainThread(MainThread, started 3848)> name is yang
data is <__mp_main__.Data object at 0x0000027827270EF0> id(data) is 2715076202224
in thread <_MainThread(MainThread, started 12368)> name is yang
data is <__mp_main__.Data object at 0x000002420EA80E80> id(data) is 2482736991872
in thread <_MainThread(MainThread, started 4152)> name is yang
data is <__mp_main__.Data object at 0x000001B1577F0E80> id(data) is 1861188783744
0

最後的輸出是0,說明瞭子進程對於主進程傳入的Data對象操作其實對於主進程的對象是不起作用的,我們需要怎樣的操作才能實現子進程可以操作主進程的對象呢?我們可以使用multiprocessing.managers 下的 BaseManager 來實現

#coding:utf-8
import threading
import multiprocessing
from multiprocessing.managers import BaseManager

class Data:
    def __init__(self,data=None):
        self.data = data

    def get(self):
        return self.data

    def set(self,data):
        self.data = data
        
BaseManager.register("mydata",Data)

def test(name,data,lock):
    lock.acquire()
    print("in thread {} name is {}".format(threading.current_thread(),name))
    print("data is {} id(data) is {}".format(data,id(data)))
    data.set(data.get()+1)
    lock.release()



def getManager():
    m = BaseManager()
    m.start()
    return m


if __name__ == '__main__':
    manager = getManager()
    d = manager.mydata(0)
    thlist = []
    name = "yang"
    lock = multiprocessing.Lock()
    for i in range(5):
        th = multiprocessing.Process(target=test,args=(name,d,lock))
        th.start()
        thlist.append(th)
    for i in thlist:
        i.join()
    print(d.get())

使用from multiprocessing.managers import BaseManager 引入 BaseManager以後,在定義完Data類型之後,使用BaseManager.register("mydata",Data) 將Data類型註冊到BaseManager中,並且給了它一個名字叫mydata,之後就可以使用BaseManager對象的這個名字來初始化對象,我們來看一下輸出

C:\Python35\python.exe F:/python/python3Test/multask.py
in thread <_MainThread(MainThread, started 12244)> name is yang
data is <__mp_main__.Data object at 0x000001FE1B7D9668> id(data) is 2222932504080
in thread <_MainThread(MainThread, started 2860)> name is yang
data is <__mp_main__.Data object at 0x000001FE1B7D9668> id(data) is 1897574510096
in thread <_MainThread(MainThread, started 2748)> name is yang
data is <__mp_main__.Data object at 0x000001FE1B7D9668> id(data) is 2053415775760
in thread <_MainThread(MainThread, started 7812)> name is yang
data is <__mp_main__.Data object at 0x000001FE1B7D9668> id(data) is 2766155820560
in thread <_MainThread(MainThread, started 2384)> name is yang
data is <__mp_main__.Data object at 0x000001FE1B7D9668> id(data) is 2501159890448
5

我們看到,雖然在每個子進程中使用的是不同的對象,但是它們的值卻是可以“共用”的。

標準的數據類型也可以通過multiprocessing庫中的Value對象,舉一個簡單的例子

#coding:utf-8
import threading
import multiprocessing
from multiprocessing.managers import BaseManager

class Data:
    def __init__(self,data=None):
        self.data = data

    def get(self):
        return self.data

    def set(self,data):
        self.data = data

BaseManager.register("mydata",Data)

def test(name,data,lock):
    lock.acquire()
    print("in thread {} name is {}".format(threading.current_thread(),name))
    print("data is {} id(data) is {}".format(data,id(data)))
    data.value +=1
    lock.release()


if __name__ == '__main__':
    d = multiprocessing.Value("l",10) #
    print(d)
    thlist = []
    name = "yang"
    lock = multiprocessing.Lock()
    for i in range(5):
        th = multiprocessing.Process(target=test,args=(name,d,lock))
        th.start()
        thlist.append(th)
    for i in thlist:
        i.join()
    print(d.value)

這裡使用d = multiprocessing.Value("l",10) 初始化了一個數字類型的對象,這個類型是Synchronized wrapper for c_long ,multiprocessing.Value在初始化時,第一個參數是類型,第二個參數是值,具體支持的類型如下

還可以使用ctypes庫里和類初始化字元串

>>> from ctypes import c_char_p
>>> s = multiprocessing.Value(c_char_p, b'\xd1\xee\xd1\xe5\xd0\xc7')
>>> print(s.value.decode('gbk'))
楊彥星

還可以使用Manager對象初始list,dict等

#coding:utf-8
import multiprocessing


def func(mydict, mylist):
    # 子進程改變dict,主進程跟著改變
    mydict["index1"] = "aaaaaa" 
    # 子進程改變List,主進程跟著改變 
    mydict["index2"] = "bbbbbb"
    mylist.append(11)  
    mylist.append(22)
    mylist.append(33)


if __name__ == "__main__":
    # 主進程與子進程共用這個字典
    mydict = multiprocessing.Manager().dict()
    # 主進程與子進程共用這個List
    mylist = multiprocessing.Manager().list(range(5))  

    p = multiprocessing.Process(target=func, args=(mydict, mylist))
    p.start()
    p.join()

    print(mylist)
    print(mydict)

其實我們這裡所說的共用只是數據值上的共用,因為在多進程中,各自持有的對象都不相同,所以如果想要同步狀態需要曲線救國。不過這種在自己寫的小項目中可以簡單的使用,如果做一些大一點的項目,還是建議不要使用這種共用數據的方式,這種大大的增加了程式間的耦合性,使用邏輯變得複雜難懂,所以建議還是使用隊列或者資料庫作為通信的渠道。

參考文章
Python 進程之間共用數據(全局變數)

Python多進程編程-進程間共用數據


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • [toc] 運用領域模型 交流與語言的使用 非原創,感謝《領域驅動設計》這本書 領域模型可成為軟體項目通用語言的核心。該模型是一組得自於項目人員頭腦中的概念,以及反映了領域深層含義的術語和關係。這些術語和相互關係提供了模型語言的語義,雖然語言是為領域量身定製的,但就技術開發而言,其依然足夠精確。正是 ...
  • [toc] 運用領域模型 消化知識 非原創,感謝《領域驅動設計》這本書 有效建模的要素 (1) 模型和實現的綁定。最初的原型雖然簡陋,但它在模型與實現之間建立了早期鏈接,而且在所有後續的迭代中我們一直在維護該鏈接。 (2) 建立了一種基於模型的語言。隨著項目的進展,雙方都能夠直接使用模型中的術語,並 ...
  • 前言 談到JAVA,就不得不提JVM JAVA程式員繞不開的話題.也許有童鞋會說,我不懂JVM,但是我一樣可以寫出JAVA代碼,我相信說這種話的童鞋,往往是只有1 3年的初級開發人員,對JAVA理解還不深,不明白JVM的重要性,那接下來我們來說說,為什麼要學習JVM? 1.理解JVM,才能幫助我們寫 ...
  • 一、BufferedWriter 1.使用帶有緩衝區的字元讀和寫進行試驗 package com.bjpowernode.java_learning; import java.io.*; ​ public class D100_1_BufferedWriter { public static voi ...
  • 隨機回去10位幸運者的名單,依次遞減,最後一位就是幸運者 1 package com.lottery.controller; 2 3 import java.io.BufferedReader; 4 import java.io.FileReader; 5 import java.io.IOExce ...
  • 線程池 導包: 回調函數非同步將可迭代對象中的元素進行某種操作 註意事項:callback必須有一個參數,且只能有一個參數 非同步主要是被應用在耗時的操作 測試:同步&非同步效率 搭建一個flask,自己啟動服務,測試執行時間 新建一個 新建一個 文件夾,在該文件夾下創建一個HTML文件,我寫的是 ,隨便 ...
  • const修飾普通成員函數 我們知道this指針指向的是具體的對象,普通的成員函數存在(加深記憶),設計this指針的時候格式為*const this(常量指針)。 const修飾變數一般有兩種方式:const T *a,或者 T const *a,這兩者都是一樣的,主要看const位於*的左邊還是 ...
  • 1. MyBatis認識 MyBatis是一款優秀的持久層框架,它支持定製化SQL、存儲過程以及高級映射。MyBatis可以使用簡單的XML配置或註解來配置和映射原生信息,將介面和Java的POJO(Plain Ordinary Java Object,普通Java對象)映射成資料庫中的數據。 2. ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...