设置
我用 Python 编写了一个非常复杂的软件(在 Windows PC 上)。我的软件基本上启动了两个 Python 解释器 shell。当您双击 main.py
文件时,第一个 shell 启动(我想)。在该 shell 中,其他线程以下列方式启动:
# Start TCP_thread
TCP_thread = threading.Thread(name = 'TCP_loop', target = TCP_loop, args = (TCPsock,))
TCP_thread.start()
# Start UDP_thread
UDP_thread = threading.Thread(name = 'UDP_loop', target = UDP_loop, args = (UDPsock,))
TCP_thread.start()
Main_thread
启动 TCP_thread
和 UDP_thread
。尽管它们是独立的线程,但它们都在一个 Python shell 中运行。
Main_thread
也启动了一个子进程。这是通过以下方式完成的:
p = subprocess.Popen(['python', mySubprocessPath], shell=True)
从 Python 文档中,我了解到此子进程在单独的 Python 解释器会话/shell 中 同时运行(!) 。这个子进程中的 Main_thread
完全专用于我的 GUI。 GUI 为所有通信启动 TCP_thread
。
我知道事情变得有点复杂。因此,我在这张图中总结了整个设置:
我有几个关于此设置的问题。我会在这里列出它们:
问题 1 [ 已解决]
Python 解释器一次只使用一个 CPU 内核来运行所有线程,这是真的吗? In other words, will the Python interpreter session 1
(from the figure) run all 3 threads ( Main_thread
, TCP_thread
and UDP_thread
) on one CPU核?
回答:是的,这是真的。 GIL(全局解释器锁)确保所有线程一次都在一个 CPU 内核上运行。
问题2 [ 尚未解决]
我有办法追踪它是哪个 CPU 内核吗?
问题3 [ 部分解决]
这道题我们忘记了 threads ,而是关注Python中的 subprocess 机制。启动一个新的子进程意味着启动一个新的 Python 解释器 实例。这个对吗?
回答:是的,这是正确的。起初对于以下代码是否会创建一个新的 Python 解释器实例存在一些疑惑:
p = subprocess.Popen(['python', mySubprocessPath], shell = True)
问题已经澄清。这段代码确实启动了一个新的 Python 解释器实例。
Python 是否足够聪明,可以让单独的 Python 解释器实例在不同的 CPU 核心上运行?有没有一种方法可以跟踪哪一个,也许还有一些零星的打印语句?
问题 4 [ 新问题]
社区讨论提出了一个新问题。产生新进程时显然有两种方法(在新的 Python 解释器实例中):
# Approach 1(a)
p = subprocess.Popen(['python', mySubprocessPath], shell = True)
# Approach 1(b) (J.F. Sebastian)
p = subprocess.Popen([sys.executable, mySubprocessPath])
# Approach 2
p = multiprocessing.Process(target=foo, args=(q,))
第二种方法有一个明显的缺点,它只针对一个函数——而我需要打开一个新的 Python 脚本。无论如何,这两种方法在实现的目标上是否相似?
原文由 K.Mulier 发布,翻译遵循 CC BY-SA 4.0 许可协议
不是。GIL 和 CPU 亲和性是不相关的概念。 GIL 可以在阻塞 I/O 操作期间释放,无论如何在 C 扩展中进行长时间的 CPU 密集型计算。
如果一个线程在 GIL 上被阻塞;它可能不在任何 CPU 核心上,因此可以公平地说,纯 Python 多线程代码在 CPython 实现中一次可能只使用一个 CPU 核心。
我认为 CPython 不会隐式管理 CPU 亲和性。它很可能依赖于操作系统调度程序来选择运行线程的位置。 Python 线程是在真实操作系统线程之上实现的。
要找出可用 CPU 的数量:
同样,线程是否被调度到不同的 CPU 上并不取决于 Python 解释器。
我想,一个特定的 CPU 可能会从一个时隙更改为另一个时隙。您可以 在旧的 Linux 内核上查看类似
/proc/<pid>/task/<tid>/status
的内容。在我的机器上,task_cpu
可以从/proc/<pid>/stat
或/proc/<pid>/task/<tid>/stat
读取:对于当前的便携式解决方案,请查看
psutil
是否公开此类信息。您可以将当前进程限制为一组 CPU:
是的。
subprocess
模块创建新的操作系统进程。如果您运行python
可执行文件,那么它会启动一个新的 Python 解释器。如果您运行 bash 脚本,则不会创建新的 Python 解释器,即运行bash
可执行文件不会启动新的 Python 解释器/会话/等。见上文(即,操作系统决定在哪里运行你的线程,并且可能有操作系统 API 公开线程运行的位置)。
multiprocessing.Process
还创建了一个新的操作系统进程(运行一个新的 Python 解释器)。Python 使用模块来组织代码。 If your code is in
another_file.py
thenimport another_file
in your main module and passanother_file.foo
tomultiprocessing.Process
.multiprocessing.Process()
可能在subprocess.Popen()
之上实现。multiprocessing
提供类似于threading
API的API,它抽象出python进程之间通信的细节(Python对象如何序列化以在进程之间发送)。如果没有 CPU 密集型任务,那么您可以在单个进程中运行 GUI 和 I/O 线程。如果您有一系列 CPU 密集型任务,那么要同时使用多个 CPU,要么使用具有 C 扩展的多个线程,例如
lxml
,regex
,numpy
(或您自己使用 Cython 创建的)可以在长时间计算期间释放 GIL 或将它们卸载到单独的进程中(一种简单的方法是使用进程池,例如concurrent.futures
提供的进程池)。“方法 1(a)” 在 POSIX 上是错误的(尽管它可能适用于 Windows)。为了可移植性,请使用 “方法 1(b)” ,除非您知道您需要
cmd.exe
(在这种情况下传递一个字符串,以确保使用正确的命令行转义)。subprocess
创建新进程, 任何 进程,例如,您可以运行 bash 脚本。multprocessing
用于在另一个进程中运行Python代码。 导入 Python 模块并运行其功能比将其作为脚本运行更加灵活。请参阅 使用 subprocess 在 python 脚本中使用输入调用 python 脚本。