Python多线程与多进程数据共享机制及解决方案
本文深入探讨Python中多线程与多进程的数据共享差异。多线程下标准类型与自定义对象均共享同一内存ID,需注意线程安全;多进程间数据默认隔离,仅小整数可能复用ID。文章通过示例演示如何利用multiprocessing.managers.BaseManager实现跨进程对象共享,并介绍Value、Manager等标准库工具的使用场景与局限性。
在编写多线程或多进程程序时,通常各子任务独立运行,彼此间无需频繁交互。若需通信,常规做法是使用队列或数据库。然而,近期在涉及共享变量的多线程与多进程代码开发中,发现数据共享机制存在若干需要注意的关键点。
多线程环境下的数据共享
多线程环境下的数据共享
标准数据类型在线程间的共享行为
参考以下代码示例
#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()
此处定义一个全局整型变量d,初始值为5。当5个子线程调用test函数并将d作为参数传入时,这5个线程是否拥有同一个d?通过test函数内部打印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类型时,发现它们的id变得不同……
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均不相同。回过头来在多线程中使用列表、元组和字典,结果它们的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,符合预期。但如果放到多进程中呢?因为在多进程下,每个子进程所持有的对象是不同的,所以每个子进程操作的是各自的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,说明了子进程对于主进程传入的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
我们看到,虽然在每个子进程中使用的是不同的对象,但是它们的值却是可以“共享”的。
标准的数据类型也可以通过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中多线程与多进程中的数据共享问题,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对本站网站的支持! 如果你觉得本文对你有帮助,欢迎转载,烦请注明出处,谢谢!
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













