1、什么是进程?

进程:操作系统提供的抽象概念,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。程序是指令、数据及其组织形式的描述,进程是程序的实体。程序本身是没有生命周期的,它只是存在磁盘上的一些指令,程序一旦运行就是进程。

2、python如何实现多进程?

在python中,利用multiprocessing可以实现多进程。multiprocessing是一个支持使用与 threading 模块类似的 API 来产生进程的包。 multiprocessing 包同时提供了本地和远程并发操作,通过使用子进程而非线程有效地绕过了 全局解释器锁。 因此,multiprocessing 模块允许程序员充分利用给定机器上的多个处理器。 它在 Unix 和 Windows 上均可运行。

https://docs.python.org/zh-cn/3.8/library/multiprocessing.html?highlight=process#module-multiprocessing

3、实现多进程?

在 multiprocessing 中,通过创建一个 Process 对象然后调用它的 start() 方法来生成进程。需要说明的以下几个方法的区别:

run():表示进程活动的方法。调用此方法仅仅执行方法,并不会产生进程;

start():启动进程活动。这个方法每个进程对象最多只能调用一次。它会将对象的 run() 方法安排在一个单独的进程中调用。

join([timeout])如果可选参数 timeout 是 None (默认值),则该方法将阻塞,直到调用 join() 方法的进程终止。如果 timeout 是一个正数,它最多会阻塞 timeout 秒。请注意,如果进程终止或方法超时,则该方法返回 None 。检查进程的 exitcode 以确定它是否终止。一个进程可以被 join 多次。进程无法join自身,因为这会导致死锁。尝试在启动进程之前join进程是错误的。

"""
使用多进程导入multiprocessing中的Process;
模拟两个任务的执行,每次任务执行有暂停时间,并输出进程的ID和父ID
"""


from multiprocessing import Process
import time
import os


def task1(*args):
    while True:
        print("执行{}, ID:{}, PPID:{}".format(args[0], os.getpid(), os.getppid()))
        time.sleep(args[1])  # 睡眠时间


def task2(*args):
    while True:
        print("执行{}, ID:{}, PPID:{}".format(args[0], os.getpid(), os.getppid()))
        time.sleep(args[1])


if __name__ == '__main__':
    print('父进程ID:', os.getpid())  # 两个子进程创建之前,先打印出父进程ID
    print('------------------------')
    t1 = Process(target=task1, args=('任务一', 1))  # 子进程1执行任务一
    t2 = Process(target=task2, args=('任务二', 2))  # 子进程2执行任务二
    t1.start()  # start()方法来生成进程
    t2.start()


结果:
E:\python数据结构与算法\day\Scripts\python.exe E:/python数据结构与算法/python_BB/process.py
父进程ID: 4564
------------------------
执行任务一, ID:9008, PPID:4564
执行任务二, ID:13972, PPID:4564
执行任务一, ID:9008, PPID:4564
执行任务一, ID:9008, PPID:4564
执行任务二, ID:13972, PPID:4564
执行任务一, ID:9008, PPID:4564
执行任务二, ID:13972, PPID:4564
执行任务一, ID:9008, PPID:4564

Process finished with exit code -1

4、进程池?

当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,
但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行。进程池主要分为阻塞式和非阻塞式。这里主要讲解阻塞式:全部添加到队列中,立刻返回,并没有等待其他的进程完毕,但是回调函数是等待任务完成之后才调用。也就是说,非阻塞式,当进程池满时,其他进程等待,一旦进程池的某一个进程结束就执行等待的进程。

from multiprocessing import Pool
import time
import os
import random


def task(task_name):
    print('开始做任务啦!', task_name)
    start = time.time()
    time.sleep(random.random() * 2)
    end = time.time()
    print('完成任务:{}!用时:{},进程id:{}'.format(task_name, (end - start), os.getpid()))


if __name__ == '__main__':
    pool = Pool(5)  # 进程池最多可以容纳5个进程
    task_name = ['听音乐', '吃饭', '洗衣服', '打游戏', '散步', '看孩子', '做饭']
    for i in task_name:
        pool.apply_async(task, args=(i, ))

    pool.close()  # 向主进程添加任务结束
    pool.join()  # 阻塞主进程,也就是说只有子进程结束主进程才能结束
    print('over!')



结果:
E:\python数据结构与算法\day\Scripts\python.exe E:/python数据结构与算法/python_BB/processpoll.py
开始做任务啦! 听音乐
开始做任务啦! 吃饭
开始做任务啦! 洗衣服
开始做任务啦! 打游戏
开始做任务啦! 散步
完成任务:洗衣服!用时:0.1972365379333496,进程id:9520
开始做任务啦! 看孩子
完成任务:散步!用时:0.30571460723876953,进程id:17224完成任务:打游戏!用时:0.30571460723876953,进程id:14588

开始做任务啦! 做饭
完成任务:听音乐!用时:1.2883315086364746,进程id:12108
完成任务:吃饭!用时:1.452604055404663,进程id:17512
完成任务:看孩子!用时:1.7680611610412598,进程id:9520
完成任务:做饭!用时:1.8028697967529297,进程id:17224
over!

Process finished with exit code 0

5、进程间通信?

Python 提供了多种实现进程间通信的机制,主要有以下 2 种:

(1) Python multiprocessing 模块下的 Queue 类,提供了多个进程之间实现通信的诸多方法;

(2) Pipe,又被称为“管道”,常用于实现 2 个进程之间的通信,这 2 个进程分别位于管道的两端。

queue:

"""
利用多进程模拟下载文件程序:
开启两个进程进行文件下载,然后再开启一个进程保存下载的文件
"""


from multiprocessing import Queue, Process
import time
import random


def download1(q, args):
    for i in args:
        q.put(i)
        print('正在下载文件:{}'.format(i))
        time.sleep(random.random()*2)


def download2(q, args):
    for i in args:
        q.put(i)
        print('正在下载文件:{}'.format(i))
        time.sleep(random.random()*2)


def savefile(q):
    while True:
        file = q.get()
        print('文件{}保存成功'.format(file))


if __name__ == '__main__':
    q = Queue(5)  # 队列中可以保存的元素上限,只有元素取出后,才会有下一个进入
    args1 = ['url1', 'url3', 'url5', 'url7']
    args2 = ['url2', 'url4', 'url6']
    down1 = Process(target=download1, args=(q, args1, ))
    down2 = Process(target=download2, args=(q, args2, ))
    save = Process(target=savefile, args=(q, ))
    down1.start()
    down2.start()
    save.start()
    down1.join()
    down2.join()
    save.terminate()
    print('over!')


结果:
E:\python数据结构与算法\day\Scripts\python.exe E:/python数据结构与算法/python_BB/process_queue.py
正在下载文件:url2
正在下载文件:url1
文件url2保存成功
文件url1保存成功
正在下载文件:url4
文件url4保存成功
正在下载文件:url3
文件url3保存成功
正在下载文件:url5
文件url5保存成功
正在下载文件:url6
文件url6保存成功
正在下载文件:url7
文件url7保存成功
over!

Process finished with exit code 0

pipe:

使用 Pipe 实现进程通信,首先需要调用 multiprocessing.Pipe() 函数来创建一个管道。该函数的语法格式如下:

conn1, conn2 = multiprocessing.Pipe( [duplex=True] )

其中,conn1 和 conn2 分别用来接收 Pipe 函数返回的 2 个端口;duplex 参数默认为 True,表示该管道是双向的,即位于 2 个端口的进程既可以发送数据,也可以接受数据,而如果将 duplex 值设为 False,则表示管道是单向的,conn1 只能用来接收数据,而 conn2 只能用来发送数据。

其中

send(obj)

发送一个 obj 给管道的另一端,另一端使用 recv() 方法接收。需要说明的是,该 obj 必须是可序列化的,如果该对象序列化之后超过 32MB,则很可能会引发 ValueError 异常。

recv()

接收另一端通过 send() 方法发送过来的数据。

from multiprocessing import Pipe, Process
import time
import random


def download1(p, args):
    for i in args:
        p.send(i)
        print('正在下载文件:{}'.format(i))
        time.sleep(random.random()*2)


def savefile(p):
    while True:
        file = p.recv()
        print('文件{}保存成功'.format(file))


if __name__ == '__main__':
    p = Pipe()
    args1 = ['url1', 'url3', 'url5', 'url7']
    down1 = Process(target=download1, args=(p[0], args1, ))
    save = Process(target=savefile, args=(p[1], ))
    down1.start()
    save.start()
    down1.join()
    save.terminate()
    print('over!')


结果:
E:\python数据结构与算法\day\Scripts\python.exe E:/python数据结构与算法/python_BB/process_pipe.py
正在下载文件:url1
文件url1保存成功
正在下载文件:url3
文件url3保存成功
正在下载文件:url5
文件url5保存成功
正在下载文件:url7
文件url7保存成功
over!

Process finished with exit code 0