python 2.7 multiprocessing 共享字典 dict 中的进程安全性 线程安全性测试

在开发过程中,需要使用多进程多线程来进行高性能开发,目的是cpu跑满,带宽跑满,但是在使用过程中发现很多共享变量、调用优先级的问题。

本文对python 2.7 的multiprocessing模块中的共享字典的线程安全性进行测试。直接上完成代码进行分析。

#!/usr/bin/python
# coding=utf-8

'''
测试 multiprocessing 中 dict 的共享特征
'''


import json
import threading
import os
from multiprocessing import Process, Lock, Manager


# 测试 共享 dict 中 常规 list 如何能够添加
def deal(lock, share_dict):
    share_dict[os.getpid()] = ["a"]
    lock.acquire()
    mydict = dict(share_dict)  # 注意,共享dict无法直接dumps,会报类型错误,必须先转换为普通字典
    json.dumps(mydict)
    print mydict
    lock.release()


# 测试 共享 dict 中 常规 list 如何能够添加
def deal2(lock, share_dict):
    for i in range(10):
        # lock.acquire()
        share_dict["hello"] += 1
        # lock.release()
    lock.acquire()
    mydict = dict(share_dict)  # 注意,共享dict无法直接dumps,会报类型错误,必须先转换为普通字典
    json.dumps(mydict)
    print mydict
    lock.release()


# 测试线程
def thread_test(share_dcit):
    share_dcit["index"] += 1
    pass


# 测试在多线程下共享变量的线程安全性
def deal3(share_dcit):
    threads_num = 20
    for i in range(threads_num):
        t = threading.Thread(target=thread_test, args=(share_dcit, ))
        t.daemon = True
        t.start()
    pass


def test():
    p_num = 10
    process = list()
    lock = Lock()
    m = Manager()
    share_dict = m.dict()  # 多进程共享变量 字典
    for i in xrange(p_num):
        process.append(Process(target=deal, args=(lock, share_dict, )))
        # share_dict["hello"] = 0
        # process.append(Process(target=deal2, args=(lock, share_dict, )))
        # share_dict["index"] = 100
        # process.append(Process(target=deal3, args=(share_dict, )))
    for p in process:
        p.start()
    for p in process:
        p.join()
    print share_dict


if __name__ == '__main__':
    test()

本段代码主要进行了三段测试,deal() 函数测试共享dict中常规list如何能够添加问题,deal2()测试共享dict中的进程安全性,deal3()测试共享dict的线程安全性。

deal() 共享dict中list添加元素

在进行功能测试时,发现对于共享dict中的list元素,使用append是没有办法添加新元素的

def deal(lock, share_dict):
    share_dict[os.getpid()] = ["a"]
    share_dict[os.getpid()].append("b")
    lock.acquire()
    mydict = dict(share_dict)  # 注意,共享dict无法直接dumps,会报类型错误,必须先转换为普通字典
    json.dumps(mydict)
    print mydict
    lock.release()

使用append后可以看到共享dict中没有添加进“b”。
在这里插入图片描述
将append()函数替换为 + 后,可以正常添加元素

def deal(lock, share_dict):
    share_dict[os.getpid()] = ["a"]
    # share_dict[os.getpid()].append("b")
    share_dict[os.getpid()] += ["b"]
    lock.acquire()
    mydict = dict(share_dict)  # 注意,共享dict无法直接dumps,会报类型错误,必须先转换为普通字典
    json.dumps(mydict)
    print mydict
    lock.release()

在这里插入图片描述

共享dict的进程安全性

将示例代码的注释进行修改

# process.append(Process(target=deal, args=(lock, share_dict, )))
share_dict["hello"] = 0
process.append(Process(target=deal2, args=(lock, share_dict, )))
# share_dict["index"] = 100
# process.append(Process(target=deal3, args=(share_dict, )))

此段代码修改共享变量中 share_dict[“hello”] 计数器,在不适用lock机制的情况下,可以看到执行图如下图,程序设置的10个进程,每个进程中循环自增了10次,正产情况计数器应该在100,二现在只到了83,说明在进行自增计算时出现了并发错误。


使用lock机制加锁,可以看到计数器正常了,到了100.

lock.acquire()
share_dict["hello"] += 1
lock.release()

在这里插入图片描述

共享dict的线程安全

将示例代码的注释进行修改

# process.append(Process(target=deal, args=(lock, share_dict, )))
# share_dict["hello"] = 0
# process.append(Process(target=deal2, args=(lock, share_dict, )))
share_dict["index"] = 100
process.append(Process(target=deal3, args=(share_dict, )))

线程安全测试中,使用了10个进程,每个进程启动20个线程,按照正常计算,计数器share_dcit[“index”]应该到200,看一下现在计数结果。
在这里插入图片描述
计数器最终只到了77,说明在线程运行过程中,出现了大量的并发问题。

使用进程所 进程锁进行加锁时,即

def thread_test(lock, share_dcit):
    lock.acquire()
    share_dcit["index"] += 1
    lock.release()
    pass

发现并发问题更重了,可以看到,进程所对线程并不起作用
在这里插入图片描述
想到了将线程锁传入进程中使用,如下图,可以看到使用线程锁python会报错。
在这里插入图片描述

结论

  1. multiprocesses中共享变量不具备进程安全和线程安全性,在使用过程中要自己进行加锁操作。同时,对某些特殊属性深拷贝机制也需要在后面的使用中进一步研究。
  2. 多个进程中同时开启多个线程,目前没有找到python直接可以进行变量控制的方法,会继续在网上查找,可以采用的替代手段是直接将结果保存在dict或者其他共享变量中,然后在主进程在对此变量进行统计,而不采用在直接的计数器方式。

去看了看源代码,发现共享dict的注册源码是

SyncManager.register('Queue', Queue.Queue)
SyncManager.register('JoinableQueue', Queue.Queue)
SyncManager.register('Event', threading.Event, EventProxy)
SyncManager.register('Lock', threading.Lock, AcquirerProxy)
SyncManager.register('RLock', threading.RLock, AcquirerProxy)
SyncManager.register('Semaphore', threading.Semaphore, AcquirerProxy)
SyncManager.register('BoundedSemaphore', threading.BoundedSemaphore,
                     AcquirerProxy)
SyncManager.register('Condition', threading.Condition, ConditionProxy)
SyncManager.register('Pool', Pool, PoolProxy)
SyncManager.register('list', list, ListProxy)
SyncManager.register('dict', dict, DictProxy)
SyncManager.register('Value', Value, ValueProxy)
SyncManager.register('Array', Array, ArrayProxy)
SyncManager.register('Namespace', Namespace, NamespaceProxy)

# types returned by methods of PoolProxy
SyncManager.register('Iterator', proxytype=IteratorProxy, create_method=False)
SyncManager.register('AsyncResult', create_method=False)

注册函数如下,看的不是很懂,也不想研究了-_-,没有找到有类似Lock机制的地方

@classmethod
def register(cls, typeid, callable=None, proxytype=None, exposed=None,
             method_to_typeid=None, create_method=True):
    '''
    Register a typeid with the manager type
    '''
    if '_registry' not in cls.__dict__:
        cls._registry = cls._registry.copy()

    if proxytype is None:
        proxytype = AutoProxy

    exposed = exposed or getattr(proxytype, '_exposed_', None)

    method_to_typeid = method_to_typeid or \
                       getattr(proxytype, '_method_to_typeid_', None)

    if method_to_typeid:
        for key, value in method_to_typeid.items():
            assert type(key) is str, '%r is not a string' % key
            assert type(value) is str, '%r is not a string' % value

    cls._registry[typeid] = (
        callable, exposed, method_to_typeid, proxytype
        )

    if create_method:
        def temp(self, *args, **kwds):
            util.debug('requesting creation of a shared %r object', typeid)
            token, exp = self._create(typeid, *args, **kwds)
            proxy = proxytype(
                token, self._serializer, manager=self,
                authkey=self._authkey, exposed=exp
                )
            conn = self._Client(token.address, authkey=self._authkey)
            dispatch(conn, None, 'decref', (token.id,))
            return proxy
        temp.__name__ = typeid
        setattr(cls, typeid, temp)

在这两天的工作中,想到了一个可以控制多进程中多线程共享变量访问的方法。方法是在使用共享变量时,先用单个进程内的线程锁加锁,再用进程锁加锁,这样就能保证共享变量在同一时间只被一个线程访问。

# 测试线程3 总计运行 10 * 20 * 1000 次
# 未加锁 Total time 1.55900001526   count=2576
# 加锁 Total time 1.45799994469 count=200000
def thread_test3(process_lock, thread_lock, count):
    for i in range(1000):
        thread_lock.acquire()
        process_lock.acquire()
        count.value += 1
        process_lock.release()
        thread_lock.release()
    pass


# 测试在多线程 使用进程锁+线程锁是否能够保证变量的线程安全
def deal5(process_lock, count):
    threads_num = 20
    threads = []
    for i in range(threads_num):
        thread_lock = threading.Lock()
        t = threading.Thread(target=thread_test3, args=(process_lock, thread_lock, count, ))
        threads.append(t)
        t.daemon = True
        t.start()
    for thread in threads:
        thread.join()
    pass
发布了16 篇原创文章 · 获赞 1 · 访问量 302

猜你喜欢

转载自blog.csdn.net/m0_46232048/article/details/104115434
今日推荐