Python 3.8 shared_memory resource_tracker在应用程序关闭时产生意外警告


  • 我使用的是multiprocessing.Pool,它调用一个或多个子流程中的函数来生成一大块数据
  • 工作进程创建一个multiprocessing.shared_memory.SharedMemory对象,并使用shared_memory指定的默认名称
  • worker将SharedMemory对象的字符串名称返回给主进程
  • 在主进程中,SharedMemory对象被链接、消耗,然后取消链接&关闭

关机时,我看到来自resource_tracker:的警告

/usr/local/lib/python3.8/multiprocessing/resource_tracker.py:216: UserWarning: resource_tracker: There appear to be 10 leaked shared_memory objects to clean up at shutdown
warnings.warn('resource_tracker: There appear to be %d '
/usr/local/lib/python3.8/multiprocessing/resource_tracker.py:229: UserWarning: resource_tracker: '/psm_e27e5f9e': [Errno 2] No such file or directory: '/psm_e27e5f9e'
warnings.warn('resource_tracker: %r: %s' % (name, e))
/usr/local/lib/python3.8/multiprocessing/resource_tracker.py:229: UserWarning: resource_tracker: '/psm_2cf099ac': [Errno 2] No such file or directory: '/psm_2cf099ac'
<8 more similar messages omitted>

由于我在主进程中取消了共享内存对象的链接,我对这里发生的事情感到困惑。我怀疑这些消息发生在子流程中(在本例中,我使用大小为1的流程池进行了测试)。

以下是一个可重复性最低的示例:

import multiprocessing
import multiprocessing.shared_memory as shared_memory
def create_shm():
shm = shared_memory.SharedMemory(create=True, size=30000000)
shm.close()
return shm.name
def main():
pool = multiprocessing.Pool(processes=4)
tasks = [pool.apply_async(create_shm) for _ in range(200)]
for task in tasks:
name = task.get()
print('Getting {}'.format(name))
shm = shared_memory.SharedMemory(name=name, create=False)
shm.close()
shm.unlink()
pool.terminate()
pool.join()
if __name__ == '__main__':
main()

我发现在我自己的笔记本电脑(Linux Mint 19.3)上运行这个例子运行得很好,但在两台不同的服务器机器上运行它(未知的操作系统配置,但两者都不同)确实存在问题。在所有情况下,我都是从docker容器中运行代码,所以Python/software-config是相同的,唯一的区别是Linux内核/主机操作系统。

我注意到此文档可能与以下内容相关:https://docs.python.org/3.8/library/multiprocessing.html#contexts-和启动方法

我还注意到;"泄漏的共享内存对象";每次跑步都会有所不同。由于我在主进程中取消了链接,然后立即退出,也许这个resource_tracker(我认为它是一个单独的进程)在主进程退出之前还没有收到更新。我对resource_tracker的作用还不够了解,无法完全理解我刚刚提出的建议。

相关主题:

  • https://bugs.python.org/issue39959

理论上,基于SharedMemory的当前实现,应该会出现警告。主要原因是,您创建的每个共享内存对象都会被跟踪两次:首先,当它由Pool对象中的一个进程生成时;第二,当它被主进程消耗时。这主要是因为SharedMemory的构造函数的当前实现将register作为共享内存对象,而不管create参数是设置为True还是其值是False

因此,当您在主进程中调用shm.unlink()时,您要做的是在共享内存对象的生产者(Pool中的某个进程)清理它之前完全删除它。因此,当池被破坏时,它的每个成员(如果他们有任务的话)都必须自己清理。关于泄漏资源的第一个警告可能是指Pool中的进程实际创建的共享内存对象,这些进程从未通过相同的进程获得unlinked。并且,No such file or directory警告是由于主进程具有unlinked——在Pool中的进程被破坏之前与共享内存对象相关联的文件。

链接错误报告中提供的解决方案可能会防止消耗进程产生额外的资源跟踪器,但它并不能完全防止消耗进程决定删除其未创建的共享内存对象时出现的问题。这是因为生成共享内存对象的进程在退出或被销毁之前,仍然需要进行一些清理,即一些unlinking

你没有看到这些警告,这一事实令人费解。但这很可能与操作系统调度、子进程中的未缓冲区以及创建进程池时使用的启动方法的组合有关。

相比之下,当我在机器上使用fork作为启动方法时,我会收到警告。否则,当使用spawnforkserver时,我看不到任何警告。我在您的代码中添加了参数解析,以便于测试不同的启动方法:

#!/usr/bin/env python3
# shm_test_script.py
"""
Use --start_method or -s to pick a process start method when creating a process Pool.
Use --tasks or -t to control how many shared memory objects should be created.
Use --pool_size or -p to control the number of child processes in the create pool.
"""
import argparse
import multiprocessing
import multiprocessing.shared_memory as shared_memory

def create_shm():
shm = shared_memory.SharedMemory(create=True, size=30000000)
shm.close()
return shm.name

def main(tasks, start_method, pool_size):
multiprocessing.set_start_method(start_method, force=True)
pool = multiprocessing.Pool(processes=pool_size)
tasks = [pool.apply_async(create_shm) for _ in range(tasks)]
for task in tasks:
name = task.get()
print('Getting {}'.format(name))
shm = shared_memory.SharedMemory(name=name, create=False)
shm.close()
shm.unlink()
pool.terminate()
pool.join()

if __name__ == '__main__':
parser = argparse.ArgumentParser(
description=__doc__,
formatter_class=argparse.RawDescriptionHelpFormatter
)
parser.add_argument(
'--start_method', '-s',
help='The multiproccessing start method to use. Default: %(default)s',
default=multiprocessing.get_start_method(),
choices=multiprocessing.get_all_start_methods()
)
parser.add_argument(
'--pool_size', '-p',
help='The number of processes in the pool. Default: %(default)s',
type=int,
default=multiprocessing.cpu_count()
)
parser.add_argument(
'--tasks', '-t',
help='Number of shared memory objects to create. Default: %(default)s',
default=200,
type=int
)
args = parser.parse_args()
main(args.tasks, args.start_method, args.pool_size)

考虑到fork是唯一一种最终显示警告的方法(至少对我来说),也许下面的声明实际上有一些内容:

父进程使用os.fork()派生Python解释器。这个子进程在开始时实际上与父进程相同过程父级的所有资源都由子级继承过程请注意,安全地分叉多线程进程是有问题的

如果父进程的所有资源都由子进程继承,则来自子进程的警告持续存在/传播也就不足为奇了

如果您特别喜欢冒险,可以编辑multiprocessing/resource_tracker.py,并通过将os.getpid()添加到打印的字符串中来更新warnings.warn行。例如,将任何带有"resource_tracker:"的警告更改为"resource_tracker %d: " % (os.getpid())就足够了。如果您已经这样做了,您会注意到警告来自不同的进程,这些进程既不是子进程,也不是主进程本身。

做出这些更改后,以下内容将有助于再次检查抱怨资源跟踪器是否与您的Pool大小一样多,并且它们的进程ID是否与主进程或子进程不同:

chmod +x shm_test_script.py
./shm_test_script.py -p 10 -t 50 -s fork > log 2> err
awk -F ':' 'length($4) > 1 { print $4 }' err | sort | uniq -c

它应该显示十行,每行前面都有来自相应资源跟踪器的投诉数量。每一行还应该包含一个PID,该PID应该与主进程和子进程不同。

概括一下,如果每个子进程接收到任何工作,那么它应该有自己的资源跟踪器。由于您没有显式地取消子进程中共享内存对象的链接,因此当子进程被销毁时,资源可能会被清理掉。

我希望这有助于回答你的一些问题,如果不是全部的话。

我的工作中也有类似的东西。我有几个微服务在不同的控制台中运行,因此在不同的过程中运行。

为了消除警告,我在每个微服务中创建共享内存时都注册了共享内存名称。

然后在关闭微服务时

for shared_name in self.shared_memory_list_toclean:
sm_temp = sm.SharedMemory(name=shared_name)
sm_temp.close()
sm_temp.unlink()

因为每个进程都负责取消共享内存的链接。

编辑:事实上,当一个进程使用共享内存(如果没有创建,则为enven)时,我仍然有警告的问题。我使用Diego Flores在https://bugs.python.org/issue39959

我所做的是从我的repo中的多处理复制文件shared_memory.py。并提出了解决方案:

更换

from .resource_tracker import register
register(self._name, "shared_memory")

通过在119线上

if create:
from multiprocessing.resource_tracker import register
register(self._name, "shared_memory")

然后在我的代码中使用共享内存,而不是导入:

from multiprocessing import shared_memory as sm

我会做:

import my_sharedmemory as sm

相关内容

  • 没有找到相关文章

最新更新