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

如何在毫秒级同步Python进程?,python,python-3.x,parallel-processing,multiprocessing,python-multiprocessing,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 = multipro

我试图同时在两个内核上运行两个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.000999275207519531
秒。如前所述,这种差异将对长期产生重大影响

总之,如何在两个不同的内核上同时运行两个函数?
如果Python无法做到这一点,我还应该检查哪些其他选项?

您可以为每个进程指定一个
多处理.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

你所要求的并不是通常的操作系统应该提供的。您有操作系统调度、核心迁移、通过cpu热力学变化的时钟速度、不同的缓存命中和未命中等。可以提高流程优先级,并将流程固定到某些核心(对此进行调查),但这样做不太可能带来稳定的改进。你的操作系统通常比你在这里做得更好

对于真正困难的实时约束,您必须进行研究。此外,您还必须选择一种中级语言(例如C/C++),它允许细粒度内存管理(减少代价高昂的cpu缓存未命中)。你可能要求以不同的方式做一些事情(),所以当我继续向你展示如何获得一些同步时,我不理解这是对你解决任何问题的整个方法的认可


这里选择的武器是一个
多处理障碍。这是一个同步原语,允许指定需要在barrier实例上调用
.wait()
的多个执行器(线程/进程)。当指定数量的执行器调用了
wait()
时,屏障会同时释放所有等待的执行器。这样,所有执行器都可以在这种屏障操作上同步

请注意,一个这样的操作对于您的要求是不够的。我前面提到的操作系统因素总是会带来混乱,cpu时间将再次偏离同步点。这意味着您必须在特定的时间间隔内一次又一次地重复同步。当然,这会使您损失一些吞吐量。较短的同步间隔意味着平均发散较小

下面您将看到实现该技术的两个函数
syncstart\u-foo
只同步一次(如@blhsing的答案),
sync\u-foo
每隔
sync\u间隔
迭代一次。完成所有迭代后,函数将
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()
示例输出:

同步启动的启动测试\u foo 增量:28.90毫秒 ------------------------------------------------------------ 正在启动sync_foo的测试 增量:1.38毫秒 ------------------------------------------------------------ 同步启动的启动测试\u foo 增量:70.33毫秒 ------------------------------------------------------------ 正在启动sync_foo的测试 增量:0.33毫秒 ------------------------------------------------------------ 同步启动的启动测试\u foo 增量:4.45毫秒 ------------------------------------------------------------ 正在启动sync_foo的测试 增量:0.17毫秒 ------------------------------------------------------------ 同步启动的启动测试\u foo 增量:168.80毫秒 ------------------------------------------------------------ 正在启动sync_foo的测试 增量:0.30毫秒 ------------------------------------------------------------ 同步启动的启动测试\u foo 增量:79.42毫秒 ------------------------------------------------------------ 正在启动sync_foo的测试 增量:1.24毫秒 ------------------------------------------------------------ 进程已完成,退出代码为0

您可以看到,像
syncstart\u foo
那样只同步一次是不够的。

您不能使用
time
datetime
模块使它们同步吗?比如,让它们等到同一时间才开始执行?您如何确保它们在不同的内核上运行?你有多少内核,你在运行什么操作系统?你说它们必须在同一时间启动,然后你说它们必须在很长时间内保持同步-请问哪一个?什么误差是允许的?最后,恕我直言,这个需求是如何/为什么产生的?这不是Python的问题,而是硬件问题、操作系统调度程序问题或更一般的并行处理问题。你能描述一下为什么你必须让这些过程完美地并行运行吗?因为有可能有一个更好的解决方案,你正试图实现的(在许多情况下,由于许多原因,这几乎是不可能的)?这是天才!