Python 3.8 shared_memory resource_tracker 在应用程序关闭时产生意外警告
Python 3.8 shared_memory resource_tracker producing unexpected warnings at application close
- 我正在使用
multiprocessing.Pool
,它调用 1 个或多个子进程中的函数来生成大量数据。
- 工作进程创建一个
multiprocessing.shared_memory.SharedMemory
对象并使用 shared_memory
指定的默认名称。
- worker returns 主进程的
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)上 运行 没问题,但是 运行在两个不同的服务器机器(未知 OS 配置,但两者不同)它确实出现了问题。在所有情况下,我都是从 docker 容器中 运行ning 代码,所以 Python/software 配置是相同的,唯一的区别是 Linux kernel/host OS.
我注意到此文档可能相关:https://docs.python.org/3.8/library/multiprocessing.html#contexts-and-start-methods
我还注意到“泄漏的 shared_memory 个对象”的数量与 运行 运行 不同。由于我在主进程中取消链接,然后立即退出,也许这个 resource_tracker
(我认为是一个单独的进程)在主进程退出之前没有收到更新。我不太了解 resource_tracker
的作用,无法完全理解我刚刚提出的建议。
相关主题:
理论上,根据 SharedMemory
的当前实施,应该会出现警告。主要原因是您创建的每个共享内存对象都会被跟踪两次:首先,当它由 Pool
对象中的一个进程生成时;其次,当它被主进程消耗时。这主要是因为 SharedMemory
的构造函数的当前实现将 register
共享内存对象,而不管 create
参数是否设置为 True
或其值为 False
.
因此,当您在主进程中调用 shm.unlink()
时,您正在做的是在其生产者(Pool
中的某个进程)开始清理它之前完全删除共享内存对象向上。因此,当池被销毁时,它的每个成员(如果他们有任务的话)都必须自己清理。关于泄漏资源的第一个警告可能指的是Pool
中进程实际创建的共享内存对象从未得到unlinked
通过相同的过程。而且,No such file or directory
警告是由于主进程在 Pool
中的进程被销毁之前具有 unlinked
与共享内存对象关联的文件。
the linked bug report 中提供的解决方案可能会阻止消费进程产生额外的资源跟踪器,但它并不能完全防止当消费进程决定删除共享内存对象时出现的问题没有创造。这是因为生成共享内存对象的进程在退出或被销毁之前仍然需要进行一些清理,即一些 unlinking
。
您没有看到这些警告这一事实非常令人费解。但这很可能与 OS 调度、子进程中未刷新的缓冲区以及创建进程池时使用的启动方法的组合有关。
相比之下,当我在我的机器上使用 fork
作为启动方法时,我收到了警告。否则,在使用 spawn
和 forkserver
时我看不到任何警告。我在您的代码中添加了参数解析,以便于测试不同的启动方法:
#!/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
是唯一最终显示警告的方法(至少对我而言),也许下面的陈述实际上有一些关于它的内容:
The parent process uses os.fork() to fork the Python interpreter. The
child process, when it begins, is effectively identical to the parent
process. All resources of the parent are inherited by the child
process. Note that safely forking a multithreaded process is
problematic.
如果父进程的所有资源都被子进程继承,子进程的警告persist/propagate也就不足为奇了。
如果您特别喜欢冒险,可以编辑 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() > 1 { print }' err | sort | uniq -c
那应该显示十行,每行前面都有来自相应资源跟踪器的投诉数。每行还应包含一个 PID,该 PID 应不同于主进程和子进程。
总而言之,如果每个子进程收到任何工作,它都应该有自己的资源跟踪器。由于您没有明确取消链接子进程中的共享内存对象,因此当子进程被销毁时,资源可能会被清理。
我希望这有助于回答您的部分(如果不是全部)问题。
- 我正在使用
multiprocessing.Pool
,它调用 1 个或多个子进程中的函数来生成大量数据。 - 工作进程创建一个
multiprocessing.shared_memory.SharedMemory
对象并使用shared_memory
指定的默认名称。 - worker returns 主进程的
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)上 运行 没问题,但是 运行在两个不同的服务器机器(未知 OS 配置,但两者不同)它确实出现了问题。在所有情况下,我都是从 docker 容器中 运行ning 代码,所以 Python/software 配置是相同的,唯一的区别是 Linux kernel/host OS.
我注意到此文档可能相关:https://docs.python.org/3.8/library/multiprocessing.html#contexts-and-start-methods
我还注意到“泄漏的 shared_memory 个对象”的数量与 运行 运行 不同。由于我在主进程中取消链接,然后立即退出,也许这个 resource_tracker
(我认为是一个单独的进程)在主进程退出之前没有收到更新。我不太了解 resource_tracker
的作用,无法完全理解我刚刚提出的建议。
相关主题:
理论上,根据 SharedMemory
的当前实施,应该会出现警告。主要原因是您创建的每个共享内存对象都会被跟踪两次:首先,当它由 Pool
对象中的一个进程生成时;其次,当它被主进程消耗时。这主要是因为 SharedMemory
的构造函数的当前实现将 register
共享内存对象,而不管 create
参数是否设置为 True
或其值为 False
.
因此,当您在主进程中调用 shm.unlink()
时,您正在做的是在其生产者(Pool
中的某个进程)开始清理它之前完全删除共享内存对象向上。因此,当池被销毁时,它的每个成员(如果他们有任务的话)都必须自己清理。关于泄漏资源的第一个警告可能指的是Pool
中进程实际创建的共享内存对象从未得到unlinked
通过相同的过程。而且,No such file or directory
警告是由于主进程在 Pool
中的进程被销毁之前具有 unlinked
与共享内存对象关联的文件。
the linked bug report 中提供的解决方案可能会阻止消费进程产生额外的资源跟踪器,但它并不能完全防止当消费进程决定删除共享内存对象时出现的问题没有创造。这是因为生成共享内存对象的进程在退出或被销毁之前仍然需要进行一些清理,即一些 unlinking
。
您没有看到这些警告这一事实非常令人费解。但这很可能与 OS 调度、子进程中未刷新的缓冲区以及创建进程池时使用的启动方法的组合有关。
相比之下,当我在我的机器上使用 fork
作为启动方法时,我收到了警告。否则,在使用 spawn
和 forkserver
时我看不到任何警告。我在您的代码中添加了参数解析,以便于测试不同的启动方法:
#!/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
是唯一最终显示警告的方法(至少对我而言),也许下面的陈述实际上有一些关于它的内容:
The parent process uses os.fork() to fork the Python interpreter. The child process, when it begins, is effectively identical to the parent process. All resources of the parent are inherited by the child process. Note that safely forking a multithreaded process is problematic.
如果父进程的所有资源都被子进程继承,子进程的警告persist/propagate也就不足为奇了。
如果您特别喜欢冒险,可以编辑 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() > 1 { print }' err | sort | uniq -c
那应该显示十行,每行前面都有来自相应资源跟踪器的投诉数。每行还应包含一个 PID,该 PID 应不同于主进程和子进程。
总而言之,如果每个子进程收到任何工作,它都应该有自己的资源跟踪器。由于您没有明确取消链接子进程中的共享内存对象,因此当子进程被销毁时,资源可能会被清理。
我希望这有助于回答您的部分(如果不是全部)问题。