Python 对于多处理,为什么要按顺序执行

Python 对于多处理,为什么要按顺序执行,python,python-multiprocessing,Python,Python Multiprocessing,我在网上通过一些例子学习如何并行编程,即如何使用多处理 我在Windows10上运行,带有spyder 3.3.6和python 3.7 import os import time from multiprocessing import Process, Queue def square(numbers, queue): print("started square") for i in numbers: queue.put(i*i) print(

我在网上通过一些例子学习如何并行编程,即如何使用多处理

我在Windows10上运行,带有spyder 3.3.6和python 3.7

import os
import time
from multiprocessing import Process, Queue

def square(numbers, queue):
    print("started square")
    for i in numbers:
        queue.put(i*i)
        print(i*i)
    print(f"{os.getpid()}")

def cube(numbers, queue):
    print("started cube")
    for i in numbers:
        queue.put(i*i*i)
        print(i*i*i)
    print(f"{os.getpid()}")

if __name__ == '__main__':

    numbers = range(5)
    queue = Queue()

    square_process = Process(target=square, args=(numbers,queue))
    cube_process = Process(target=cube, args=(numbers,queue))

    square_process.start()
    cube_process.start()

    square_process.join()
    cube_process.join()

    print("Already joined")
    while not queue.empty():
        print(queue.get())
我期望队列的输出是混合的或不确定的,因为它取决于进程启动的速度或第一个进程完成所有语句的速度? 理论上,我们可以得到0,1,4,8,9,27,16,64。 但实际输出是按如下顺序进行的
0 1. 4. 9 16 0 1. 8. 27
64

这里没有什么需要理解的

  • 两个进程分别执行square和cube函数。在函数中,它们将维持由for循环控制的顺序
  • 在某个时间点上唯一随机的部分是-“哪个进程正在执行并向队列添加什么”。所以它可能是正方形过程在第五次迭代(i=4),而立方体过程在第二次迭代(i=1)
  • 您正在使用队列的单个实例添加来自两个分别执行square和cube函数的进程的项。队列是先进先出(FIFO),因此当您从队列中获取(在主线程中打印)时,它将保持接收项目的顺序
执行以下更新版本的程序,以便更好地理解

import os
import time
from multiprocessing import Process, Queue

def square(numbers, queue):
    print("started square process id is %s"%os.getpid())    
    for i in numbers:
        queue.put("Square of %s is %s "%(i, i*i))        
        print("square: added %s in queue:"%i)    

def cube(numbers, queue):    
    print("started cube process id is %s"%os.getpid())    
    for i in numbers:
        queue.put("Cube of %s is %s "%(i, i*i*i))
        print("cube: added %s in queue:"%i)


if __name__ == '__main__':

    numbers = range(15)
    queue = Queue()

    square_process = Process(target=square, args=(numbers,queue))
    cube_process = Process(target=cube, args=(numbers,queue))

    square_process.start()
    cube_process.start()

    square_process.join()
    cube_process.join()

    print("Already joined")
    while not queue.empty():
        print(queue.get())

很确定这只是因为旋转一个进程需要一些时间,所以它们倾向于互相追逐

我重写它是为了让作业有更好的并行运行的机会:

from multiprocessing import Process, Queue
from time import time, sleep


def fn(queue, offset, start_time):
    sleep(start_time - time())
    for i in range(10):
        queue.put(offset + i)


if __name__ == '__main__':
    queue = Queue()
    start_time = time() + 0.1

    procs = []
    for i in range(2):
        args = (queue, i * 10, start_time)
        procs.append(Process(target=fn, args=args))

    for p in procs: p.start()
    for p in procs: p.join()

    while not queue.empty():
        print(queue.get())

我应该注意到,我得到了输出的不确定性排序,正如您所期望的那样。我在Linux下,所以在Windows下你可能会得到一些不同的东西,但我认为不太可能

看起来MisterMiyagi是对的。启动额外的python进程比计算从0到4的平方要昂贵得多:)我已经用lock原语创建了一个版本的代码,现在我们确定进程是同时启动的

import os
from multiprocessing import Process, Queue, Lock


def square(numbers, queue, lock):

    print("started square")
    # Block here, until lock release
    lock.acquire()
    for i in numbers:
        queue.put(i*i)
    print(f"{os.getpid()}")

def cube(numbers, queue, lock):
    # Finally release lock
    lock.release()
    print("started cube")
    for i in numbers:
        queue.put(i*i*i)
    print(f"{os.getpid()}")

if __name__ == '__main__':

    numbers = range(5)
    queue = Queue()
    lock = Lock()
    # Activate lock
    lock.acquire()
    square_process = Process(target=square, args=(numbers,queue,lock))
    cube_process = Process(target=cube, args=(numbers,queue,lock))

    square_process.start()
    cube_process.start()

    cube_process.join()
    square_process.join()

    print("Already joined")
    while not queue.empty():
        print(queue.get())
我的输出是:

0
0
1
4
1
9
8
16
27
64

这些进程本身并没有做任何CPU繁重或网络受限的事情,所以执行它们所花费的时间可以忽略不计。我的猜测是,当第二个过程开始时,第一个过程已经完成。进程本质上是并行的,但由于您的任务非常琐碎,因此会产生一种错觉,即它们是按顺序运行的。您可以在脚本中引入一些随机性,以查看运行中的并行性

import os
from multiprocessing import Process, Queue
from random import randint

from time import sleep

def square(numbers, queue):

    print("started square")

    for i in numbers:
        if randint(0,1000)%2==0:
            sleep(3)
        queue.put(i*i)
        print(i*i)
    print(f"square PID : {os.getpid()}")

def cube(numbers, queue):

    print("started cube")
    for i in numbers:
        if randint(0,1000)%2==0:
            sleep(3)
        queue.put(i*i*i)
        print(i*i*i)
    print(f"cube PID : {os.getpid()}")

if __name__ == '__main__':

    numbers = range(5)
    queue = Queue()

    square_process = Process(target=square, args=(numbers,queue))
    cube_process = Process(target=cube, args=(numbers,queue))

    square_process.start()
    cube_process.start()

    square_process.join()
    cube_process.join()

    print("Already joined")
    while not queue.empty():
        print(queue.get())

在这里,两个进程随机暂停其执行,因此当一个进程暂停时,另一个进程有机会向队列中添加一个数字(multiprocessing.queue是线程和进程安全的)。如果您多次运行此脚本,您将看到队列中项目的顺序并不总是相同的

Python有一个GIL,它可以序列化不同线程之间的操作。这些进程运行多长时间?第二个在第一个完成之前就开始了吗?我猜Mistermiagi的解释是正确的。第二个进程在第一个进程完成之前还没有开始,在正方形中放入睡眠(0.005)后,最后一行打印结果0 0 1 8 27 64 1 4 9 16,这确实意味着这两个进程是并行执行的,尽管看起来这两个进程上有某种锁