如何在毫秒级同步Python进程?

时间:2019-03-28 22:40:32

标签: python python-3.x parallel-processing multiprocessing python-multiprocessing

我正在尝试完全同时在两个内核上使用两个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无法做到这一点,我还应该检查其他哪些选项?

2 个答案:

答案 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)是不够的。