学习笔记-python多进程

e多进程

进程的实质就是就是正在执行的程序

底层的cpu级别中根本就不会出现多个程序一起执行的情况,多核cpu能够同时执行对应核数的程序,cpu使用的是时间片段的方式进行程序执行,由于切换的非常快,看起来就像是同时执行多个程序

为了看清实际的动作,使用的fork()进行编程,fork()只能在linux中执行,windows执行会直接报错

代码:

import os

def test1():
    for i in range(3):
        print('进程1')

def test2():
    for i in range(5):
        print('进程2')


pid = os.fork() # 返回当前进程的子进程id,由于这里是分裂进程,子进程实际上在执行这一句的时候是没有pid值的,因此系统给的值是0
pids = os.fork()
if pid == 0: # 表示子进程,pid的值实际上是当前进程的子进程id,如果当前就是子进程则id=0
    test1()
else:
    test2()

print('执行结束')

程序在执行到os.fork()的时候进行自动的分裂,主进程分裂一个子进程,这里存在两个fork()

在第一个pid=os.fork()的时候存在一个主进程一个子进程,在主进程和子进程执行pids=os.fork()的时候主进程再次分裂一个子进程,二原来的子进程执行继续分裂为两个子进程.所以此时整个程序存在四个进程

进程id的查询

os.getpid() # 查询当前的进程的进程id
os.getppid() # 查询当前进程的父进程id

当一个进程进行分裂的时候,主进程还是原来的进程,分裂的子进程的父进程id能够在子进程查到,但是子进程没有继续分裂则子进程的子进程id为0

进程的内存复制性质

当进程进行分裂的一瞬间,主进程实际上存在自己的内存中间中会有很多变量,而子进程是新建立的,实际上子进程会在分裂的时候复制主进程中的内存去开辟一个新内存,那么这两个内存实际上相互独立,因此在分裂之前定义的变量实际上是双份,子进程中的各自对变量进行操作,其结果将互不影响

import os
# 关于全局变量的操作
num = 0
pid = os.fork()
# 创建多进程
if pid == 0:
    num += 1
    print(f'{num}')
else:
    num += 2
    print(f'{num}')

# 因为多进程的状态下实际上在执行进程分裂的时候子进程就会复制父进程的内存空间
输出结果1,2

能够跨平台使用的多进程模块

使用的是multiprocessing中的Process

这是一个类,想使用这个类先实例化一个对象,类上面存在多个方法,当实例化对象的时候类本身是没有立即执行进程分裂,而在执行start方法的时候才分裂,实例化对象的时候只是进行对象参数的传入和初始化

代码入下

import time
from multiprocessing import Process  # 导入多进程模块

import os


def test1(args):
    time.sleep(1)
    print(f'{os.getpid()}子进程1进程号,传入的参数{type(args)}')
    print(f'父进程id{os.getppid()}')
def test2(args1,args2):
    time.sleep(2)
    print(f"{os.getpid()}子进程2进程号,传入的参数{args1,args2}")
    print(f'父进程id{os.getppid()}')

def main():
    # 创建进程对象
    p1 = Process(target=test1,args=(1,)) # 第一个进程
    p1.start() # 启动进程
    p2 = Process(target=test2,args=(10,12)) # 第二个进程
    p2.start()
    # 主进程执行
    print(f'主进程main{os.getpid()}')

if __name__ == '__main__':
    main()

其中参数的传入有点坑,本身Process上面有args和kwargs两个参数,前一个是传一般的参数,后一个传的是关键字参数,一般的位置参数通常是一个可迭代的对象,在实例化的时候要么是元组要么是列表,而kwargs是关键字参数,直接传入的是字典

target表示分裂的进程需要执行对应的函数的函数名

在函数的接收的时候,如果传入的args不止一个元素,那么函数就要接收对应个数的元素,不用去计较参数名,但是一旦是kwargs参数则表明是关键字参数,参数名必须一致

from multiprocessing import Process

def test(x,y,key1,key2):
    print(f'args参数{x}和{y},kwags参数为:{key1,key2}')

def main():
    p1 = Process(target=test,args=[1,2],kwargs={'key1':10,'key2':20})
    # 启动p1分裂进程
    p1.start()
    print('结束')

if __name__ == '__main__':
    main()

执行结果:

结束
args参数12,kwags参数为:(10, 20)

使用类的多进程写法

import os
import time
from multiprocessing import Process
# 进行类的写法
class MyProcess(Process):
    def __init__(self,args,**kwargs):
        # 可以有两种方式进行父类的初始化
        super(MyProcess, self).__init__()
        # 或者使用的是
        # Process.__init__()
        self.args = args
        self.kwargs = kwargs

    def run(self): # 这里必须写run,固定格式,在对象start的时候就自动执行这里的run
        time.sleep(2)
        print(f'在类里面进行进程执行{os.getpid()},参数为{self.args},关键字参数为:{self.kwargs}')


if __name__ == '__main__':
    # 实例化一个对象
    my_dict = {'key1':0,'key2':1}
    p1 = MyProcess(111,**my_dict)
    p1.start()
    # p1.join()
    print('主进程结束')

类的进程写法中需要注意的是类方法的问题,必须写run,这种是固定的写法,在继承父类的情况写写init的时候需要将父类的初始化方法执行,否则会报错

进程池

进程池说白了就是自行设定的容器,如果有三个容器,需要执行的进程有10个,则每个容器只能执行一个进程,多余7个在外面排队等着,三个正在执行的进程谁先结束,则会空一个容器出来,那么其余的7个进程只能有一个进到容器中进行执行,就像排队一样,这就是进程池的作用,你可以自己规定容器的个数

from multiprocessing import Pool # 导入进程池
import os
import random
import time
def show(i):
    print(f'这里是进程:{os.getpid()},循环次数{i}')
    # 随机睡眠时间
    time.sleep(random.random()*2)

if __name__ == '__main__':
    pool = Pool(3) # 创建三个容器
    # 进行循环开启进程
    for i in range(10):
        pool.apply_async(show,(i,)) # 开启异步执行
    # 开启进程池之后进行关闭进程池
    pool.close()
    # 执行等待所有的进程执行完成
    pool.join()
    # 最后执行主线程
    print('主线程结束')

多进程之间的通信

进程与进程之间实际上是两个独立的内存空间,那么变量之间是没哟办法进行传递的,如果需要进行传递,则需要进行公共的内存来保存两个进程之间的变量信息,这个公共保存变量的内存空间实际上是利用队列的存储方式进行存储,先进的先出存储方式.因此如果需要进行变量的传递,进程不能直接传到另一个进程中,而是先将需要的数据传递到中间的公共内存部分,也就是队列里面,另外一个进程则从公共的部分里面进行获取数据,从而实现进程之间的通信

采用的是Queue的方式进行传递,Queue就像一个公共存储的地方,两个进程可以从中进程数据的拿取

import time
from multiprocessing import Process,Queue
# 进程之间的通信使用的是队列的方式
def put_function(q):
    data = ['string','list','dict',{'key':'int'}]
    for i in data:
        q.put(i)
    print('将data存进中间的队列')

def get_function(q):
    time.sleep(2) # 保证put里面有数据,该进程先执行则将直接结束,所以进行延迟
    while True:
        data = q.get()
        if data: # 判定队列中是否有值,如果有则输出
            print(data)
        else:
            break

if __name__ == '__main__':
    # 创建队列
    q = Queue() # 通常并不限制队列中的数据个数
    p1 = Process(target=put_function, args=(q,))
    p2 = Process(target=get_function, args=(q,))
    # 以上是创建了两个进程但是还没有执行
    # 执行p1进程
    p1.start()
    p2.start()

这里比较难受的是当如果不进行设置时间延迟,则可能导致p2进程先向队列中进行数据拿取,而此时p1还没有将数据put进队列,从而直接到时p2进程进入break结束掉,这里进行延迟就是为了保证p2在拿数据的时候存在数据,当然为了更安全起见还可以在p2.start()之前进行p1.join()操作,在p1没有结束之前不执行后面的代码

猜你喜欢

转载自blog.csdn.net/weixin_43959953/article/details/84887794