人工智能(PythonNet)—— 多进程(fork函数、multiprocessing模块、进程池)

        为了解决一个程序能够并发处理多个任务,所以在操作系统中引入了多进程、多线程;此处主要讨论多进程,多进程编程分为两种:1、由操作系统内核提供的fork函数完成多进程的创建、销毁等操作;2、由第三方模块(multiprocessing)提供更为专业的多进程操作。此外,还需要注意进程池的应用(多用于频繁的创建、销毁较多进程的情况)。

一、多进程基本概念

1、父子进程

        在操作系统中除了初始化进程(PID=1,系统内核启动的第一个用户级进程,是所有其它进程的父进程)之外,每个进程都有一个父进程,可能有0个或者多个子进程。由此形成父子进程关系。我们认为每个进程都是父进程发起请求创建的。

        进程信息查看:
                进程树:        pstree
                父进程PID :  ps  -ajx

2、孤儿、僵尸进程

        孤儿进程:父进程先于子进程退出,此时子进程变为孤儿进程
        孤儿进程会被系统指定的进程所“收养”,即该进程成为孤儿进程新的父进程。在孤儿进程退出时,“继父”会进行处理不会使其成为僵尸。

        僵尸进程:子进程先于父进程退出,但父进程并没有处理子进程的退出状况,子进程就会成为僵尸进程。
        僵尸进程会滞留PCB的部分信息在内存中,大量的僵尸进程会消耗系统资源,所以应该尽量避免僵尸进程的产生

3、如何避免僵尸进程的产生

        a、让父进程先退出
        b、让父进程处理子进程的退出

二、fork函数

        import  os        # 引入系统模块

1、fork函数

        pid = os.fork()
        功    能:创建一个新的进程
        参    数:无
        返回值:失败   -1
                      成功   0              在新的进程(子进程)中返回
                                正整数      在原有进程(父进程)中的返回新的进程的PID
        说明:
                * 子进程会复制父进程全部代码段,包括fork前的代码
                * 子进程从fork的下一句开始执行
                * 父子进程通常会根据fork返回值的差异选择执行不同的代码 (使用if结构)
                * 父子进程在执行上互不干扰,执行顺序不确定
                * 子进程虽然复制父进程内存空间,但是有自己的特性,比如PID号,PCB,进程栈空间等
                * 父子进程空间独立,各自修改各自的内容,互不影响

2、进程相关函数

        os.getpid()
        功能:获取当前进程的PID号
        返回值 : 当前进程PID

        os.getppid()
        功能 : 获取当前进程父进程的PID号
        返回值 : 父进程PID

        os._exit(status)
        功能 : 结束一个进程
        参数 : 表示进程的结束状态是一个整数

        sys.exit([status])
        功能 : 结束一个进程,抛出异常
        参数 : 传入一个正整数表示结束状态
                        传入字符串表示结束打印
        * sys.exit 可以通过捕获SystemExit异常阻止退出

try:
    sys.exit("进程退出")
except SystemExit as e:
    print(e)

3、fork如何避免僵尸进程的产生

        a、让父进程先退出

                创建二级子进程
                        1)父进程创建子进程等待子进程退出
                        2)子进程创建二级子进程,然后马上退出
                        3)二级子进程成为孤儿,处理具体事件

                示例:参考 四.1.e(二级子进程)
         b、让父进程处理子进程的退出

                I)使用wait或者waitpid函数
                        pid,status = os.wait()
                        功能 :在父进程中阻塞等待处理子进程的退出
                        返回值 : pid  退出的子进程的PID号
                                            status  子进程的退出状态
                    
                        pid,status = os.waitpid(pid,option)
                        功能 : 同wait
                        参数 : pid   -1  表示任意子进程退出
                                                    >0  整数  指定PID号的子进程退出
                            option   0  表示阻塞等待
                                         WNOHANG  表示非阻塞
                        返回值 : 同wait

                        注:waitpid(-1,0)   ======  wait()
                II)使用信号处理
                        #处理僵尸进程
                        signal.signal(signal.SIGCHLD,signal.SIG_IGN)

三、multiprocessing 标准库模块创建进程

        1. 需要将要做的事件封装为函数
        2. 使用multiprocessing中提供的Process类创建进程对象
        3. 通过进程对象和Process初始化函数对进程进行设置,并且绑定要执行的事件
        4. 启动进程,会自动执行相关联函数
        5. 事件完成后回收进程

        import multiprocessing    # 导入标准库模块

1、创建子进程类

        p = multiprocessing.Process()
        功能:创建子进程
        参数:name   :给创建的进程起一个名字
                                     默认 process-1
                    target :目标函数
                    args   :元组  给target指定的函数传递的参数位置
                    kwargs :字典  给target指定的函数传递的参数键值
        返回:由目标函数产生的进程对象

2、进程对象的属性和函数


        p.start()
        功能 : 启动子进程此时进程真正创建

        p.join([timeout])
        功能 : 阻塞等待回收相应的子进程
        参数 : 默认为阻塞,timeout为超时时间

        属性:
                p.name  进程名称
                p.pid   创建的进程的PID号
                p.is_alive()  进程状态

                p.daemon
                默认值为False 表示主进程结束后不影响子进程的执行
                如果设置为True 则主进程执行完毕所有的子进程一同退出
                说明:
                          * 设置必须在 start()前
                          * 一般使用daemon = True时不用加join
                          * 该属性并不是 linux/unix系统中所说的守护进程设置
                补充:
                        守护进程 : 生命周期长,随系统创建随系统销毁。
                                           不受前端控制,后台运行
                                           操作系统进程,或者是自动化运行进程居多

3、特点

        * multiprocessing创建进程是原来进程的子进程,创建后父子进程各自执行互不影响
        * 子进程同样是复制父进程的空间,子进程对内容的修改不会影响父进程空间
        * join回收子进程,会有效的阻止僵尸进程产生

4、创建自己的进程类

        a、继承Process类
        b、重写__init__ 并且调用父类的__init__
        c、重写run方法,此时生成对象后调用start就会自动运行run

四、进程池

        原因:如果有大量任务需要多进程完成,且可能需要频繁的创建和删除进程,给计算机带来大量的资源消耗。
        解决方法:在进程池内运行一定数量进程,通过这些进程完成进程池队列中的事件,直到事件执行完毕,减少进程不断的创建删除过程。

        进程池处理事件的流程:
                1. 创建进程池,在池内放入适量的进程
                2. 将事件加入到进程池等待队列
                3. 使用进程池中的进程不断处理事件
                4. 所有事件处理后,回收关闭进程池

        from multiprocessing import Pool     # 导入库

1、进程池相关函数

        pool = Pool(processes)
        功能 : 创建进程池
        参数 : processes指定进程池中进程数量,一般根据计算机内核个数来确定
        返回 : pool表示得到进程池对象

        pool.apply_async(func,args,kwds)
        功能:异步方式将事件放入进程池执行
        参数:func  要执行的事件
                    args  给func用元组传参
                                kwds  给func用字典传参
        返回值:返回一个对象,该对象可以通过get()方法得到func函数的返回值

        pool.close()
        功能:关闭进程池,使其无法加入新的事件
        pool.join()
        功能: 阻塞等待进程池退出 (当所有事件处理完毕后)

        pool.apply()
        用法和apply_async一样,只是需要顺序执行,一个事件结束在执行另一个事件

        pool.map(func,iter)
        功能:将需要完成的事件放入进程池;类似于内建函数map
        参数: func 需要完成的事件函数
                   iter 可迭代对象给func传参
        返回值:func事件函数的返回值列表

r = pool.map(fun,test)
r = []
for i in test:
		res = pool.apply_async(fun,(i,))
		r.append(res.get())

五、示例

1、fork函数

        a、基本功能及变量作用域
import os
from time import sleep 

#fork之前的部分只有父进程执行
print("======================")
a = 1

pid = os.fork()

if pid < 0:
    print("创建进程失败")
elif pid == 0:
    sleep(1)
    print("a = ", a)
    a = 10000
    print("新创建的进程 child a = ", a)
else:
    sleep(2)
    print("原来的进程 parent a = ", a)

print("程序执行完毕") 
        b、进程pid
import os 
from time import sleep

pid = os.fork()

if pid < 0:
    print("Create process failed")
elif pid == 0:   
    print("Child process")
    print("getpid() :",os.getpid()) #子进程自己获取自己的PID
    print("getppid() :",os.getppid()) #子进程获取它父进程的PID
    print("========================")
else:
    sleep(1)
    print("Parent process")
    print("pid =",pid) #父进程fork返回值就是子进程PID
    print("getpid():",os.getpid())#父进程获取自己的PID
        c、wait函数
import os,sys 
from time import sleep

pid = os.fork()

if pid < 0:
    print("create process failed")
elif pid == 0:
    sleep(3)
    print("子进程PID:",os.getpid())
    sys.exit(3)
else: 
    #等待子进程退出
    pid,status = os.wait()
    print(pid,status)
    print(os.WEXITSTATUS(status)) #获取退出状态
    while True:
        pass  
        d、waitpid函数
import os,sys 
from time import sleep

pid = os.fork()

if pid < 0:
    print("create process failed")
elif pid == 0:
    sleep(3)
    print("子进程PID:",os.getpid())
    sys.exit(3)
else: 
    #等待子进程退出
    while True:
        sleep(1)
        pid,status = os.waitpid(-1,os.WNOHANG)
        print(pid,status)
        if os.WEXITSTATUS(status):
            break
        print("do something others")
    while True:
        pass  
           e、二级子进程
#创建二级子进程
import os 
from time import sleep 

def fun1():
    sleep(3)
    print("第一件事情")

def fun2():
    sleep(4)
    print("第二件事情")

# 第一次创建
pid = os.fork()

if pid < 0:
    print("Create process error")
elif pid == 0:
    # 创建二级进程
    pid0 = os.fork()
    if pid0 < 0:
        print("创建二级进程失败")
    elif pid0 == 0:
        fun2()  #做第二件事
    else:
        os._exit(0)
else:
    os.wait()
    fun1() #做第一件事

2、multiprocessing标准库Process函数

        a、多进程
from multiprocessing import Process 
from time import sleep 
import os 

def th1():
    sleep(3)
    print("吃饭")
    print(os.getppid(),"----",os.getpid())

def th2():
    sleep(2)
    print("睡觉")
    print(os.getppid(),"----",os.getpid())

def th3():
    sleep(4)
    print("打豆豆")
    print(os.getppid(),"----",os.getpid())

things = [th1,th2,th3]
process = []

for th in things:
    p = Process(target = th)
    process.append(p) #保存进程对象
    p.start()

for p in process:
    p.join()
        b、多进程传参
from multiprocessing import Process 
from time import sleep 

def worker(sec,name):
    for i in range(3):
        sleep(sec)
        print("I'm %s"%name)
        print("I'm working.....")

#通过args给函数传参
#通过kwargs给函数传参
p = Process(name = "Worker",target = worker,args = (2,),\
    kwargs = {'name':'Levi'})
p.start()

#判断进程状态
print("is alive :",p.is_alive())

#进程名
print("process name:",p.name)

#子进程PID
print("process PID:",p.pid)

p.join()

print("=====Process over========")
        c、daemon属性
from multiprocessing import Process 
from time import sleep,ctime 

def tm():
    while True:
        sleep(2)
        print(ctime())

p = Process(target = tm)

#在start前设置daemon为True
p.daemon = True 

p.start()
sleep(5)
print("main process over")

3、进程池Pool函数

        a、进程池操作
from multiprocessing import Pool 
from time import sleep,ctime 

#事件函数
def worker(msg):
    sleep(2)
    print(msg)
    return msg + "over"

#创建进程池,放4个进程
pool = Pool(processes = 4)

result = []
for i in range(10):
    msg = "hello %d"%i
    #将事件放入进程池
    r = pool.apply_async(func = worker,args = (msg,))
    #保存返回值对象
    result.append(r)
    # pool.apply(func = worker,args = (msg,))

#关闭进程池
pool.close()

#回收进程池
pool.join()

#获取事件函数的返回值
for i in result:
    print(i.get())
        b、进程池map函数
from multiprocessing import Pool 
import time 

def fun(n):
    time.sleep(1)
    print("执行 pool map事件")
    return n * n 

#创建进程池
pool = Pool(4)
#使用map将事件放入进程池
r = pool.map(fun,range(6))

print("返回值列表r",r)
pool.close()
pool.join()

六、附录:目录

        人工智能(PythonNet)—— 目录汇总



猜你喜欢

转载自blog.csdn.net/qq_27297393/article/details/81031426
今日推荐