欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

python多进程与多线程(二):使用多进程

程序员文章站 2022-07-12 21:32:02
...

什么是进程?

额,进程呢就是你运行一个程序,这时他就启动了一个进程。。。(真的没啥好说的了,)


不同进程的关系

每一个进程都有自己的资源,并且是不共享的,就像你家的鸡不会送给邻居家吃(特例除外。。。)

子进程的资源是将主进程的资源进行了拷贝,虽然你操作的变量名字一样,但是眼前人已非彼时人,最后输出,唯有失望!


如何使用多进程

python使用多进程一共有三个包可以用,因为multiprocessing这个包兼容性好,所以我们用它来做介绍。

要注意,在Windows环境下要将主进程代码写在if __name__=='__main__':下面

因为操作系统问题,Windows系统会把后面的代码也拷贝,导致形成无限递归,会一直报错,但是写在if __name__=='__main__':下面之后,系统会自动判断。


多进程开始

我们可以试使用multiprocessing包中的Process方法创建多进程

from multiprocessing import Process
def process1(x):
    print(x)
    for i in range(1,4):
        print(i,'*'*15,1)
def process2(x):
    print(x)
    for i in range(5,8):
        print(i,'*'*15,2)

if __name__ == '__main__':
    first = Process(target = process1,args=(1,))
    second = Process(target = process2,args=(2,))
    first.start()
    second.start()

锵锵锵,我们在主进程里面创建了两个子进程哦。

在这里我对代码做一个解释:

  • Process表示创建一个进程
  • target表示进程要调用的函数,注意不要写小括号,写了的话就成了实现函数了,不是参数了
  • args表示传入函数的参数,当参数只有一个的时候要在最后面写上逗号,我这里只是为了体现有这个功能才写的参数x
  • start表示进程开始执行,但各个进程的执行顺序不是按代码执行的,是cpu分配的,可能每次和每次都不同

代码加上个join

from multiprocessing import Process
def process1(x):
    print(x)
    for i in range(1,4):
        print(i,'*'*15,1)
def process2(x):
    print(x)
    for i in range(5,8):
        print(i,'*'*15,2)

if __name__ == '__main__':
    first = Process(target = process1,args=(1,))
    second = Process(target = process2,args=(2,))
    first.start()
    first.join
    second.start()

这个代码不管你执行多少次,都是先执行process1,然后执行process2。这是join函数的作用。

我们使用进程.join(),就表示告诉主进程,等这个子进程执行完了再干别的事


join、start、run都是什么意思

start方法中包含run方法,其实写run和start的效果都是差不多的。

join的意思是当前进程执行完再执行别的进程,通常用于进程同步。


多进程不能同步全局变量

我们重新写两个进程,一个修改全局变量,一个输出全局变量,看看是什么效果

from multiprocessing import Process

x = 10

def process1():
    global x
    for i in range(1,4):
        x += 1
    print(x,'process1')

def process2():
    global x
    print(x,'process2')  # 看看能不能得到process1处理后的x
    x -=1
    print(x,'process2')


if __name__ == '__main__':
    first = Process(target = process1)
    second = Process(target = process2)
    first.start()
    first.join()
    second.start()
    print(x,'main')  # 判断全局变量有没有被改变

这是因为,每个进程都是将主进程的资源copy了一份,对本进程的变量进行修改,不是对主进程的变量进行修改!


如何使用多进程同步全局变量

可以试用管道或队列的方法,在这里,推荐使用队列的方法,因为队列方便操作。

队列是一种先进先出的数据结构,python的队列可以放置任意数据类型,常用函数如下

函数 描述
q.empty() 判断队列是否为空
q.put() 在队列末尾添加一个元素
q.get() 取出队列第一个元素

我们在传参数的时候,要将队列作为参数一起传进去,修改后的代码如下:

from multiprocessing import Process,Queue

def process1(q):
    for i in range(3):
        q.put(i)

def process2(q):
    while not q.empty():
        print(q.get())

if __name__ == '__main__':
    q = Queue(5)  # 表示队列最多放五个元素
    first = Process(target = process1,args = (q,))
    second = Process(target = process2,args = (q,))
    first.start()
    first.join()
    second.start()

我们惊奇的发现,全局变量同步了诶hiahiahia,这是因为队列是写在内存上的哦


进程池简介

当我们想批量执行一个函数,又不想去用列表创建的时候,或者是异步执行的的时候,我们就可以使用进程池。

进程池进程的最大数量不能超过你cpu内核,一般来说4个进程就可以了,你可以使用下面的代码看看你是多少核的cpu

import os
print("本机为",os.cpu_count(),"核 CPU")  # 本机为4核

流量池的使用步骤如下:

  • 创建流量池
  • apply_async添加异步函数或者apply添加同步函数
  • 调用close()方法来停止添加函数
  • join()开始执行调用流量池

使用进程池同步调用函数

from multiprocessing import Pool
import time

def func(msg):
    print( "msg:", msg)
    time.sleep(0.1)
    return msg

if __name__ == "__main__":
    pool = Pool(processes = 3)
    res_l=[]
    for i in range(10):
        msg = "hello %d" %(i)
        res = pool.apply(func, (msg, ))   #维持执行的进程总数为processes,当一个进程执行完毕后会添加新的进程进去
        res_l.append(res) #同步执行,即执行完一个拿到结果,再去执行另外一个
    print("==============================>")
    pool.close()
    pool.join()   #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束

    print(res_l) #看到的就是最终的结果组成的列表
    for i in res_l: #apply是同步的,所以直接得到结果,没有get()方法
        print(i)

需要强调的是:此操作并不会在所有池工作进程中并执行func函数。

如果要通过不同参数并发地执行func函数,必须从不同线程调用p.apply()函数或者使用p.apply_async()


使用进程池异步调用函数

from multiprocessing import Pool
import time

def func(msg):
    print( "msg:", msg)
    return msg

if __name__ == "__main__":
    pool = Pool(processes = 3)
    res_l=[]
    for i in range(10):
        msg = "hello %d" %(i)
        res = pool.apply_async(func, (msg, ))
        res_l.append(res)
    print("==============================>")

    pool.close() #关闭进程池,防止进一步操作。如果所有操作持续挂起,它们将在工作进程终止前完成
    pool.join()   #调用join之前,先调用close函数,否则会出错。执行完close后不会有新的进程加入到pool,join函数等待所有子进程结束

    print(res_l) #看到的是<multiprocessing.pool.ApplyResult object at 0x10357c4e0>对象组成的列表,而非最终的结果,但这一步是在join后执行的,证明结果已经计算完毕,剩下的事情就是调用每个对象下的get方法去获取结果
    for i in res_l:
        print(i.get()) #使用get来获取apply_aync的结果,如果是apply,则没有get方法,因为apply是同步执行,立刻获取结果,也根本无需get

这个程序就代表,同一时间,有四个进程在跑这个函数


如何用进程池使用队列

使用流量池同步数据,也是使用队列,但是这里的队列来源不同

from multiprocessing import Manager
q = Manager.Queen(5)

其他操作就没啥了,把q也作为传到函数里就好了


如何获取进程编号

os库提供了获取进程编号的相关方法。

函数 描述
os.getpid() 获取当前进程的编号
os.getppid() 获取父进程的编号

特别要注意的地方

要根据情况慎用join,因为稍不留意,你的多进程(线程)程序效果还不如单进程(线程)。

因为如果你总是等一个进程执行完再去执行另外一个进程,可能执行时间比单进程还要长,建议大家使用流量池异步执行或者不写join