2 -进程-Windows 10 -Python - multiprocessing 两种方法实现多进程 - 进程池 _ 简单Process _ -守护进程堵塞join 与 sleep()线程睡眠堵塞

@

测试环境:

操作系统: Window 10
工具:Pycharm
Python: 3.7

一、进程池

其实在使用多进程的时候,感觉使用pool是最方便的,在多线程中是不存在pool的。

在使用pool的时候,可以限制每次的进程数,也就是剩余的进程是在排队,而只有在设定的数量的进程在运行,在默认的情况下,进程是cpu的个数,也就是根据multiprocessing.cpu_count()得出的结果。

进程池 map() 和 imap() 方法的实现

pool中,有两个方法,一个是map一个是imap,其实这两方法超级方便,在执行结束之后,可以得到每个进程的返回结果,但是缺点就是每次的时候,只能有一个参数,也就是在执行的函数中,最多是只有一个参数的,否则,需要使用组合参数的方法。

代码演示:


    
import multiprocessing
    
def func(name):
      print('start process {}'.format(name))
      return name.upper()
    
if __name__ == '__main__':
      p = multiprocessing.Pool(5)  # 限制每次执行的进程数为 5 个进程
      print(p.map(func,['map','map']))  # 开启一个进程
      for i in p.imap(func,['imap','imap']):  # 开启了 2 个进程
        print(i)							# 有两个参数就开启两个进程
    

运行结果:
在这里插入图片描述

在使用map的时候,直接返回的一个是一个list,从而这个list也就是函数执行的结果,而在imap中,返回的是一个由结果组成的迭代器,如果需要使用多个参数的话,那么估计需要*args,从而使用参数args

进程池 apply_async() 的实现

在使用apply_async()的时候,可以直接使用多个参数

代码演示:

#!/usr/bin/env python
import multiprocessing
import time

def func(name,plan):
    print('start process {} 计划'.format(plan))
    time.sleep(2)
    return name.upper()


if __name__ == '__main__':
    results = []
    p = multiprocessing.Pool(5) # 限制运行的进程数量为 5 个
    for i in range(7):          # 开启 7 个进程,而 7 个进程超过了 5 个,所以限制了 2 个进程,不给放行
        res = p.apply_async(func, args=('诸葛亮','B'))
        results.append(res)
    for i in results:
        print(i.get(2.1))  # 限制获取进程返回值的超时时间为 2.1 秒
        

运行结果:
在这里插入图片描述
一次只有 5 个进程在运行,另外 2 个进程,不允许运行。

在进行得到各个结果的时候,注意使用了一个list来进行append,要不然在得到结果get的时候会阻塞进程,从而将多进程编程了单进程,从而使用了一个list来存放相关的结果,在进行得到get数据的时候,可以设置超时时间,也就是get(timeout=5),这种设置。

这里使用列表来存储队列 Queue 对象,应该是提前用了 Queue 模块的 put() 方法,然后返回一个队列对象。

————————————————————————————————————————

二、简单 Process 方法

多进程的类Process和多线程的类Thread差不多的方法,两者的接口基本相同,具体看以下的代码:

代码演示:

#!/usr/bin/env python
    
from multiprocessing import Process
import os
import time
    
def func(name):
      print('start a process {}'.format(name))
      time.sleep(3)
      print('the process parent id :',os.getppid())
      print('the process id is :',os.getpid())
    
if __name__ =='__main__':
      processes = []
      for i in range(2):
        p = Process(target=func,args=(i,))
        processes.append(p)
      for i in processes:
        i.start()
      print('start all process')
      for i in processes:
        i.join()
        #pass
      print('all sub process is done!')

运行结果
在这里插入图片描述

在上面例子中可以看到,多进程和多线程的API接口是一样一样的,显示创建进程,然后进行start开始运行,然后join等待进程结束。

在需要执行的函数中,打印出了进程的id和pid,从而可以看到父进程和子进程的id号,在linux中,进程主要是使用fork出来的,在创建进程的时候可以查询到父进程和子进程的id号,而在多线程中是无法找到线程的id

python 多线程不是无法找到 id,而是因为多线程是数据共享的,所以所有的多线程都是共享一个共享 id
参考链接:
获得进程id_浅谈python中的多线程和多进程(二)

Tips:

Linux操作系统中查询的id的时候,最好用pstree,清晰。

三、守护进程堵塞 join 理解

代码演示:

import multiprocessing
import time


def B(name):

    for i in range(5):  # 执行 10 次 B 计划
        print("{}执行B 计划……".format(name))
        #time.sleep(0.5)

if __name__ == '__main__':
    funB = multiprocessing.Process(target=B, args=("诸葛亮",))
    # 设置进程守护
    funB.daemon = True
    funB.start()
    #time.sleep(3)
    funB.join()  # 选择注释,来测试有 join 和无 join 的情况

    print("A 计划完毕……")

运行结果:

无join 的结果
在这里插入图片描述
有join 的结果
在这里插入图片描述

在进行运行的时候,可以看到,如果没有join语句,那么主进程是不会等待守护子进程结束的,而是独自执行,除非为守护进程加 join(),但其实无论是哪种进程加了 join() 方法,其实都是可以堵塞主进程的,而它们堵塞的原理如下所示:(特别需要注意的是 sleep 线程睡眠的等效于 join 堵塞,不过更灵活)

在这里进程堵塞,是由系统将在工作队列内的主进程安排进了等待队列内,导致只执行在工作队列内的子进程,而不执行主进程,从而形成了一个主进程堵塞的现象。

相关引用如下所示:

第一步:当进程A执行到创建socket的语句时,操作系统会创建一个由文件系统管理的socket对象(如下图)。这个socket对象包含了发送缓冲区、接收缓冲区、等待队列等成员。

等待队列是个非常重要的结构,它指向所有需要等待该socket事件的进程。

在这里插入图片描述

第二步:当程序执行到recv时,操作系统会将进程A工作队列移动到该socket的等待队列中(如下图)。
由于工作队列只剩下了进程B和C,依据进程调度,cpu会轮流执行这两个进程的程序,不会执行进程A的程序。
所以进程A被阻塞,不会往下执行代码,也不会占用cpu资源。

在这里插入图片描述
参考链接:
终于想明白操作系统的中断和进程阻塞了

四、进程的 sleep() 线程睡眠理解

sleep() 针对的是线程,又因为每一个进程都有一个主线程,所以即使是在主进程创建子进程后,在子进程的执行函数内,sleep() 线程睡眠仍旧是对子进程起作用,因为sleep() 线程睡眠针对的是子进程的主线程。

posted @ 2023-03-24 16:45  Muxiu  阅读(385)  评论(0编辑  收藏  举报