luizalabs / shared-memory-dict

A very simple shared memory dict implementation
MIT License
161 stars 24 forks source link

The multiprocessing.Lock used is not available across python interpreters #61

Open wnark opened 1 year ago

wnark commented 1 year ago

I wrote a demo, and when I was able to run it, I found that I could not successfully calculate 1kw additions I tried to use the decorator method of the lock in the lock.py file to create and use the lock in the child process instead of creating it in the main process and passing it to the child process.

#! /usr/bin/env python3
# _*_ coding : UTF-8 _*_
# ------------------------------ #
# 尝试使用from multiprocessing import Lock作为锁
# ------------------------------ #

from multiprocessing import Process, shared_memory

import time

# 初始化锁,用法是函数装饰器
import os
from functools import wraps

if os.getenv('SHARED_MEMORY_USE_LOCK') == '1':
    from multiprocessing import Lock
else:

    class Lock:  # type: ignore
        def acquire(self):
            pass

        def release(self):
            pass

_lock = Lock()

def lock(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        _lock.acquire()
        try:
            return func(*args, **kwargs)
        finally:
            _lock.release()

    return wrapper
#——————————————————————#
@lock
def add(x):
    x = x + 1
    return x

def fn1(shmem_name: str, n: int) -> None:
    """
    shmem_name: 共享内存块的唯一标识
    width: 内存字节大小
    n: 操作次数

    """
    # 获取内存坐标
    shmem_list = shared_memory.ShareableList(name=shmem_name)

    for _ in range(n):
        # 加了500次
        add(shmem_list[0])

    # 关闭子进程创建的内存块
    shmem_list.shm.close()

def fn2(shmem_name: str, n: int) -> None:
    """
    shmem_name: 共享内存块的唯一标识
    width: 内存字节大小
    n: 操作次数

    """
    # 获取内存坐标
    shmem_list = shared_memory.ShareableList(name=shmem_name)

    for _ in range(n):
        # 加了500次
        add(shmem_list[0])

    # 关闭子进程创建的内存块
    shmem_list.shm.close()

if __name__ == "__main__":
    # 测试下客户端+1 服务端+1 计算效率
    # 开始时间
    start_time = time.time()

    # 创建共享内存区域,由于我对大数计算没有信心能达到python的程度,因此使用list储存
    shmem_list = shared_memory.ShareableList([0])

    shmem_list[0] = 0
    # 操作次数,python可以在数字中使用_增加可读性 
    total = 10_000_000
    # 多进程运行,各加500次
    p1 = Process(target=fn1, args=(shmem_list.shm.name, total // 2))
    p2 = Process(target=fn2, args=(shmem_list.shm.name, total // 2))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    # 比较是否一致
    print(f"a[{shmem_list[0]}] == total[{total}]")
    # 关闭主进程创建的内存块
    shmem_list.shm.close()
    # 释放内存块
    shmem_list.shm.unlink()
    # 结束时间
    end_time = time.time()
    # 运行时间
    print(end_time-start_time)

Works fine when using posix_ipc as lock:

#! /usr/bin/env python3
# _*_ coding : UTF-8 _*_

# ------------------------------ #
# 尝试使用posix作为进程间的锁控制中断
# 使用vscode非debug模式运行时长:
# 只有一个单进程运算500万次:17.25112557411194
# 两个进程运行1000万次:
# 53.944918632507324
# 使用htop可以观察到cpu的kernel(红色区域)占用很高,存在内核切换,而且cpu利用不充分
# ------------------------------ #
import time

import posix_ipc
from multiprocessing import Process, shared_memory

# 设置锁的全局命名信号量
global_variable = '/my_semaphore_uuid'
# global_variable (str): 系统全局命名信号量,命名信号量的名称必须以正斜杠(/)开头,这样才会视为系统范围内的全局资源。
semaphore = posix_ipc.Semaphore(global_variable, flags=posix_ipc.O_CREAT, initial_value=1)
# 初始化的时候需要确保semaphore.value可用值为1

def fn1(shmem_name: str, n: int) -> None:
    """
    shmem_name: 共享内存块的唯一标识
    width: 内存字节大小
    n: 操作次数

    """
    # 获取内存坐标
    shmem_list = shared_memory.ShareableList(name=shmem_name)

    for _ in range (n):
        # 获取锁
        semaphore.acquire()
        # 以原子格式+1,不加锁会报错。
        shmem_list[0] +=1
        # 释放锁
        semaphore.release()

    # 关闭子进程创建的内存块
    shmem_list.shm.close()

def fn2(shmem_name: str, n: int) -> None:
    """
    shmem_name: 共享内存块的唯一标识
    width: 内存字节大小
    n: 操作次数

    """
    # 获取内存坐标
    shmem_list = shared_memory.ShareableList(name=shmem_name)

    for _ in range (n):
        # 获取锁
        semaphore.acquire()
        # 以原子格式+1,不加锁会报错。
        shmem_list[0] +=1
        # 释放锁
        semaphore.release()

    # 关闭子进程创建的内存块
    shmem_list.shm.close()

if __name__ == "__main__":
    # 测试下客户端+1 服务端+1 计算效率
    # 开始时间
    start_time = time.time()

    # 创建共享内存区域,由于我对大数计算没有信心能达到python的程度,因此使用list储存
    shmem_list = shared_memory.ShareableList([0])

    # 操作次数,python可以在数字中使用_增加可读性 
    total = 10_000_000
    # 多进程运行,各加500次
    p1 = Process(target=fn1, args=(shmem_list.shm.name, total // 2))
    p2 = Process(target=fn2, args=(shmem_list.shm.name, total // 2))
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    # 比较是否一致
    print(f"a[{shmem_list[0]}] == total[{total}]")
    # 关闭主进程创建的内存块
    shmem_list.shm.close()
    # 释放内存块
    shmem_list.shm.unlink()
    # 结束时间
    end_time = time.time()
    # 运行时间
    print(end_time-start_time)

Of course, the use of cross-python interpreters/cross-languages is a minority requirement, just as a remark.

wnark commented 1 year ago

Of course, the most efficient is the hardware atomic lock https://github.com/doodspav/atomics