以下代码是一家有5种商品的store 的代码,三位客户各需要一种商品.

import multiprocessing as mp
 
class Shop:
    def __init__(self, stock=5):
        self.stock = stock

    def get_item(self, l, x):
        l.acquire()
        if self.stock >= x:
            self.stock -= x
            print(f"{self.stock} = remaining")
        l.release()

if __name__ == "__main__":
    l = mp.Lock()
    obj = Shop()

    p1 = mp.Process(target=obj.get_item, args=(l, 1))
    p2 = mp.Process(target=obj.get_item, args=(l, 1))
    p3 = mp.Process(target=obj.get_item, args=(l, 1))

    p1.start()
    p2.start()
    p3.start()

    p1.join()
    p2.join()
    p3.join()

    print("Final: ", obj.stock)

我得到的结果如下

4 = remaining
4 = remaining
4 = remaining
Final:  5

然而,因为我用的是Lock,所以我希望它是

4 = remaining
3 = remaining
2 = remaining
Final:  2

问题:如何仅使用锁(没有进程通信,即没有管道/队列)实现上述输出?

推荐答案

此代码未按预期工作的原因是,多进程不与子进程共享其状态.这意味着您启动的每个进程p1p2p3都会获得类Shop对象的副本.它不是同一个对象.有two种方法可以解决这个问题,与进程共享实例属性stock,或者共享整个对象本身.第二种方法可能更适合于更大的用例,如果shop对象保存了需要在进程之间共享的其他数据.

Method 1:

要仅共享stock实例变量的值,可以使用multiprocessing.Value.使用此方法创建共享整数并访问其值的方法如下:

shared_int = multiprocessing.Value('i', 5)
print(f'Value is {shared_int.value}')  # 5 

根据您的用例进行调整,代码将变成:

import multiprocessing


class Shop:
    def __init__(self, stock=5):
        self.stock = multiprocessing.Value('i', stock)


    def get_item(self, l, x):
        l.acquire()
        if self.stock.value >= x:
            self.stock.value -= x
            print(f"{self.stock.value} = remaining")
        l.release()


if __name__ == "__main__":
    l = multiprocessing.Lock()
    obj = Shop()

    p1 = multiprocessing.Process(target=obj.get_item, args=(l, 1))
    p2 = multiprocessing.Process(target=obj.get_item, args=(l, 1))
    p3 = multiprocessing.Process(target=obj.get_item, args=(l, 1))

    p1.start()
    p2.start()
    p3.start()

    p1.join()
    p2.join()
    p3.join()

    print("Final: ", obj.stock.value)

Output

4 = remaining
3 = remaining
2 = remaining
Final:  2

Method 2

共享整个复杂对象是一个更复杂的过程.我最近提出了一个关于共享复杂对象(如本例中的类store 对象)的类似问题,该问题也涵盖了下面提供的代码背后的推理.我建议您阅读一下,因为它更详细地解释了底部提供的代码背后的逻辑.这个用例唯一的主要区别是您希望使用multiprocess,一个多处理分支,而不是多处理.该库的工作原理与内置的多处理相同,只是它提供了我们所需要的更好的酸洗支持.

基本上,您需要使用multiprocessing.Managers来共享状态,并使用合适的代理来访问状态.下面代码中提供的ObjProxy就是这样一个代理,它共享名称空间和实例方法(除了受保护/私有属性之外).一旦有了这些,您只需要使用管理器和代理创建Shop类的对象.这是使用新添加的Shopcreate方法完成的.这是一个类构造函数,Shop的所有对象都应仅使用此方法创建,而不是直接调用构造函数.完整代码:

import multiprocess
from multiprocess import Manager, Process
from multiprocess.managers import NamespaceProxy, BaseManager
import types


class ObjProxy(NamespaceProxy):
    """Returns a proxy instance for any user defined data-type. The proxy instance will have the namespace and
    functions of the data-type (except private/protected callables/attributes). Furthermore, the proxy will be
    pickable and can its state can be shared among different processes. """

    def __getattr__(self, name):
        result = super().__getattr__(name)
        if isinstance(result, types.MethodType):
            def wrapper(*args, **kwargs):
                return self._callmethod(name, args, kwargs)
            return wrapper
        return result


class Shop:

    def __init__(self, stock=5):
        self.stock = stock

    @classmethod
    def create(cls, *args, **kwargs):

        # Register class
        class_str = cls.__name__
        BaseManager.register(class_str, cls, ObjProxy, exposed=tuple(dir(cls)))

        # Start a manager process
        manager = BaseManager()
        manager.start()

        # Create and return this proxy instance. Using this proxy allows sharing of state between processes.
        inst = eval("manager.{}(*args, **kwargs)".format(class_str))
        return inst

    def get_item(self, l, x):
        with l:
            if self.stock >= x:
                self.stock -= x
                print(f"{self.stock} = remaining")

    def k(self, l, n):
        pass


if __name__ == "__main__":
    manager = Manager()
    l = manager.Lock()
    obj = Shop.create()
    p1 = Process(target=obj.get_item, args=(l, 1, ))
    p2 = Process(target=obj.get_item, args=(l, 1, ))
    p3 = Process(target=obj.get_item, args=(l, 1, ))

    p1.start()
    p2.start()
    p3.start()

    p1.join()
    p2.join()
    p3.join()

    print("Final: ", obj.stock) 

Output

4 = remaining
3 = remaining
2 = remaining
Final:  2

Note:这两行的说明:

manager = Manager()
l = manager.Lock()

在您的示例中,之前我们不需要为锁创建管理器(以及随后的代理)的原因如下所述.如果不创建代理,它就不能使用上面的代码,原因是我们不再在主进程中创建进程,并且锁在当前进程内存空间中不存在(因为为复杂对象创建管理器以共享其状态产生了自己的服务器进程)

Python-3.x相关问答推荐

我的SELECT函数搜索的是列,而不是列中的数据.我怎么才能让它搜索数据呢?

Numpy将3D数组的每个切片相乘以进行转置并对其求和

如何在选中项目时设置QTreeView中整行的 colored颜色 ?

如何使用Python将嵌套的XML转换为CSV

合并两个数据帧并对某些总和进行求和

通过匹配第一列的行值,逐个单元格地添加两个Pandas 数据框中的浮点数

Python 列表求和所有出现的保留顺序

DynamoDB - boto3 - batch_write_item:提供的关键元素与架构不匹配

当我判断另一个 checkButton 时,如何判断两个 python tkinter checkButtons?

Python defaultdict 在获取时返回 None,尽管使用默认值初始化

解包时是否可以指定默认值?

如何使用pandas python获取数据框中每列的最大长度

如何从同一文件夹中的模块导入功能?

TypeError:列表索引必须是整数或切片,而不是列表

无法在 Windows 8 中使用 Python 3.3 找到 vcvarsall.bat

根据条件过滤元组列表

为 Python 3 和 PyQt 构建可执行文件

python asyncio add_done_callback 与 async def

print(... sep='', '\t' ) 是什么意思?

如何从集合中删除多个元素?