我正在尝试完全同时在两个内核上使用两个Python函数。每个进程都运行一个很长的循环(理论上是无限循环)。它们必须保持同步,这一点很重要,即使是最微小的延迟也会在长期内引起问题。
我认为我的问题是我像这样顺序运行它们
# define the processes and assign them functions
first_process = multiprocessing.Process(name='p1', target='first_function')
second_process = multiprocessing.Process(name='p2', target='second_function')
# start the processes
first_process.start()
second_process.start()
我在每个功能的开始处打印了time.time()
以测量时差。输出结果是:
first function time: 1553812298.9244068
second function time: 1553812298.9254067
差异为0.0009999275207519531
秒。如前所述,这种差异从长远来看将产生重大影响。
总而言之,如何同时恰好在两个不同的内核上同时运行两个功能? 如果Python无法做到这一点,我还应该检查其他哪些选项?
答案 0 :(得分:5)
您可以为每个流程指定一个multiprocessing.Queue
对象,并在一个流程的功能开始时,使用multiprocessing.Queue.put
将一个项目放入另一个流程的队列中,然后立即尝试用multiprocessing.Queue.get
出队。由于multiprocessing.Queue.get
一直阻塞到队列中没有项目为止,因此可以有效地同步两个过程:
import multiprocessing
import time
def func(queue_self, queue_other):
queue_other.put(None)
queue_self.get()
print(time.time())
q1 = multiprocessing.Queue()
q2 = multiprocessing.Queue()
p1 = multiprocessing.Process(target=func, args=(q1, q2))
p2 = multiprocessing.Process(target=func, args=(q2, q1))
if __name__ == '__main__':
p1.start()
p2.start()
示例输出:
1553814412.7520192
1553814412.7520192
答案 1 :(得分:3)
您所要求的并不是通常的操作系统应该提供的。您有操作系统调度的干扰,核心迁移,通过cpu热力学变化的时钟速度,变化的缓存命中率和未命中率等等。可以提高进程优先级并将进程固定到某些核心(为此请查看psutil),但是这样做不太可能看到稳定的改进。通常,您的操作系统要比这里做得更好。
对于非常困难的实时约束,您必须研究RTOSes。此外,您还必须选择一种中级语言(例如C / C ++),该语言可以进行精细的内存管理(减少代价高昂的CPU缓存未命中)。无论如何,您可能都在要求以其他方式进行操作(XY problem),因此当我继续向您展示如何实现 some 同步时,您可能会不理解批准您在此处真正尝试解决的任何问题的整个方法。
这里选择的武器是multiprocessing.Barrier
。这是一个同步原语,它允许指定许多执行程序(线程/进程),这些执行程序需要在barrier-instance上调用.wait()
。当指定数量的执行者调用了wait()
时,屏障将同时释放所有等待的执行者。这样,所有执行程序都可以在这种障碍操作中同步。
请注意,这样的操作不足以满足您的要求。我前面提到的操作系统因素总是会带来混乱,而CPU时间将再次偏离同步点。这意味着您必须一次又一次地重复同步。当然,这将花费您一些吞吐量。较短的同步间隔意味着平均的散度较小。
下面您将看到两个实现该技术的函数。 syncstart_foo
仅同步一次(就像@blhsing的答案一样),sync_foo
每次sync_interval
迭代都会这样做。完成所有迭代后,函数将time.time()
返回给父级,在父级中计算时间增量。
import time
from multiprocessing import Process, Barrier, Queue
def syncstart_foo(outqueue, barrier, n_iter):
barrier.wait() # synchronize only once at start
for _ in range(int(n_iter)):
pass # do stuff
outqueue.put(time.time())
def sync_foo(outqueue, barrier, n_iter, sync_interval):
for i in range(int(n_iter)):
if i % sync_interval == 0: # will sync first time for i==0
barrier.wait()
# do stuff
outqueue.put(time.time())
用于运行基准测试的帮助器功能:
def test_sync():
"""Run test for `sync_foo`."""
special_args = (SYNC_INTERVAL,)
_run_test(sync_foo, special_args)
def test_syncstart():
"""Run test for `syncstart_foo`."""
_run_test(syncstart_foo)
def _run_test(f, special_args=None):
outqueue = Queue()
barrier = Barrier(N_WORKERS)
args = (outqueue, barrier, N_ITER)
if special_args:
args += special_args
pool = [Process(target=f, args=args) for _ in range(N_WORKERS)]
print(f'starting test for {f.__name__}')
for p in pool:
p.start()
results = [outqueue.get() for _ in range(N_WORKERS)]
for p in pool:
p.join()
print(f"delta: {(abs(results[1] - results[0])) * 1e3:>{6}.{2}f} ms")
print("-" * 60)
主要条目:
if __name__ == '__main__':
N_WORKERS = 2
N_ITER = 50e6 # 1e6 == 1M
SYNC_INTERVAL = 250_000 # synchronize every x iterations
for _ in range(5):
test_syncstart()
test_sync()
示例输出:
starting test for syncstart_foo
delta: 28.90 ms
------------------------------------------------------------
starting test for sync_foo
delta: 1.38 ms
------------------------------------------------------------
starting test for syncstart_foo
delta: 70.33 ms
------------------------------------------------------------
starting test for sync_foo
delta: 0.33 ms
------------------------------------------------------------
starting test for syncstart_foo
delta: 4.45 ms
------------------------------------------------------------
starting test for sync_foo
delta: 0.17 ms
------------------------------------------------------------
starting test for syncstart_foo
delta: 168.80 ms
------------------------------------------------------------
starting test for sync_foo
delta: 0.30 ms
------------------------------------------------------------
starting test for syncstart_foo
delta: 79.42 ms
------------------------------------------------------------
starting test for sync_foo
delta: 1.24 ms
------------------------------------------------------------
Process finished with exit code 0
您会发现,仅进行一次同步(例如syncstart_foo
)是不够的。