设置
我用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核心来运行所有线程吗?换句话说,Python interpreter session 1
(来自图)是否会在一个CPU核心上运行所有3个线程(Main_thread
,TCP_thread
和UDP_thread
)?
答:是的,这是真的。 GIL(全局解释器锁定)确保所有线程一次在一个CPU核心上运行。
问题2 [尚未解决]
我有办法跟踪它是哪个CPU核心吗?
问题3 [部分解决]
对于这个问题,我们忘记了线程,但我们专注于Python中的子进程机制。启动一个新的子流程意味着启动一个新的Python解释器实例。这是对的吗?
答案:是的,这是正确的。首先,关于以下代码是否会创建新的Python解释器实例存在一些混淆:
p = subprocess.Popen(['python', mySubprocessPath], shell = True)
该问题已得到澄清。这段代码确实启动了一个新的Python解释器实例。
Python是否足够聪明,可以在不同的CPU核心上运行单独的Python解释器实例?有没有办法跟踪哪一个,也许有一些零星的印刷陈述?
问题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脚本。无论如何,这两种方法都取得了类似的效果吗?
答案 0 :(得分:25)
问: Python解释器一次只使用一个CPU核心来运行所有线程吗?
没有。 GIL和CPU亲和力是不相关的概念。 GIL可以在阻塞I / O操作期间释放,无论如何都可以在C扩展内进行长时间的CPU密集计算。
如果线程在GIL上被阻止;它可能不在任何CPU核心上,因此可以说纯Python多线程代码在CPython实现上一次只能使用一个CPU核心。
问:换句话说,Python解释器会话1(来自图)是否会在一个CPU核心上运行所有3个线程(Main_thread,TCP_thread和UDP_thread)?
我不认为CPython会隐式管理CPU亲和力。它可能依赖于OS调度程序来选择运行线程的位置。 Python线程是在真实操作系统线程之上实现的。
问:或者Python解释器能够将它们分布在多个核心上吗?
找出可用CPU的数量:
>>> import os
>>> len(os.sched_getaffinity(0))
16
同样,线程是否在不同的CPU上进行调度并不依赖于Python解释器。
问:假设问题1的答案是“多核心”,我是否有办法跟踪每个线程正在运行的核心,可能是偶尔会有一些打印声明?如果对问题1的回答是“只有一个核心”,我是否有办法追踪它是哪一个?
我想,一个特定的CPU可能会从一个时隙变为另一个时隙。你可以look at something like /proc/<pid>/task/<tid>/status
on old Linux kernels。在我的机器上,task_cpu
can be read from /proc/<pid>/stat
or /proc/<pid>/task/<tid>/stat
:
>>> open("/proc/{pid}/stat".format(pid=os.getpid()), 'rb').read().split()[-14]
'4'
对于当前的便携式解决方案,请查看psutil
是否公开此类信息。
您可以将当前进程限制为一组CPU:
os.sched_setaffinity(0, {0}) # current process on 0-th core
问:对于这个问题,我们忘记了线程,但我们专注于Python中的子进程机制。启动一个新的子进程意味着启动一个新的Python解释器会话/ shell。它是否正确?
是。 subprocess
模块创建新的OS进程。如果你运行python
可执行文件,那么它会启动一个新的Python interpeter。如果运行bash脚本,则不会创建新的Python解释器,即运行bash
可执行文件不会启动新的Python解释器/会话/等。
问:假设它是正确的,Python是否足够聪明,可以在不同的CPU核心上运行单独的解释器会话?有没有办法跟踪这个,也许还有一些零星的印刷语句?
见上文(即操作系统决定在哪里运行你的线程,并且可能有OS API暴露线程的运行位置。)
multiprocessing.Process(target=foo, args=(q,)).start()
multiprocessing.Process
还会创建一个新的OS进程(运行一个新的Python解释器)。
实际上,我的子进程是另一个文件。所以这个例子不适合我。
Python使用模块来组织代码。如果您的代码位于主模块中的another_file.py
然后import another_file
,则将another_file.foo
传递给multiprocessing.Process
。
然而,你如何将它与p = subprocess.Popen(..)进行比较?如果我用subprocess.Popen(..)和multiprocessing.Process(..)启动新进程(或者我应该说&#39; python解释器实例&#39;),这是否重要?
multiprocessing.Process()
可能会在subprocess.Popen()
之上实施。 multiprocessing
提供类似于threading
API的API,它抽象出python进程之间的通信细节(如何在进程之间序列化Python对象)。
如果没有CPU密集型任务,那么您可以在一个进程中运行GUI和I / O线程。如果您有一系列CPU密集型任务,那么要同时使用多个CPU,要么使用带有C扩展名的多个线程,例如lxml
,regex
,numpy
(或者您自己创建的那个) Cython)可以在长时间计算期间释放GIL或将它们卸载到单独的进程中(一种简单的方法是使用由concurrent.futures
提供的进程池)。
问:社区讨论提出了一个新问题。产生新进程(在新的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,))
&#34;方法1(a)&#34; 在POSIX上出错(尽管它可能适用于Windows)。为了便于携带,请使用&#34;方法1(b)&#34; 除非您知道需要cmd.exe
(在这种情况下传递一个字符串,以确保正确的命令 - 使用了线逃逸。)
第二种方法有一个明显的缺点,它只针对一个函数 - 而我需要打开一个新的Python脚本。无论如何,这两种方法都取得了类似的效果吗?
subprocess
创建新进程,任何进程,例如,您可以运行bash脚本。 multprocessing
用于在另一个进程中运行Python代码。 导入 Python模块并运行其功能比将其作为脚本运行更灵活。请参阅Call python script with input with in a python script using subprocess。
答案 1 :(得分:3)
由于您使用的是在threading
上构建的thread
模块。如文档所示,它使用操作系统的“POSIX线程实现” pthread 。
CreatProcess()
文档page。答案 2 :(得分:1)
1,2:你有三个真正的线程,但是在CPython中它们受GIL限制,因此,假设它们正在运行纯python代码,你会看到CPU的使用情况,好像只有一个使用的核心。
3:正如所说的gdlmx,操作系统选择核心来运行线程,
但如果您真的需要控制,可以使用设置进程或线程关联
原生API通过ctypes
。因为你在Windows上,它会是这样的:
# This will run your subprocess on core#0 only
p = subprocess.Popen(['python', mySubprocessPath], shell = True)
cpu_mask = 1
ctypes.windll.kernel32.SetProcessAffinityMask(p._handle, cpu_mask)
我在这里使用私人Popen._handle
来表示简单。干净的方式是OpenProcess(p.tid)
等。
是的,subprocess
像其他新进程中的其他所有内容一样运行python。