42-Python-多任务编程-多进程+进程池

一、多任务编程

利用计算机多核特点,同时执行多个任务。通过充分利用计算机资源,来提高程序的运行效率。
实现:多进程 多线程

【1】什么叫“多任务”呢?

就是操作系统可以同时运⾏多个任务。打个 ⽐⽅,你⼀边在⽤浏览器上⽹,⼀边在听MP3,⼀边在⽤Word赶作业,这就是多任务,⾄少同时有3个任务正在运⾏。还有很多任务悄悄地在后台同时运 ⾏着,只是桌⾯上没有显示⽽已。

【2】单核CPU如何实现“多任务”呢?

操作系统轮流让各个任务交替执⾏,每个任务执⾏0.01秒,这样反复执⾏下去。 表⾯上看,每个任务交替执⾏,但CPU的执⾏速度实在是太快了,感觉就像所有任务都在同时执⾏⼀样。
在这里插入图片描述

【3】多核CPU如何实现“多任务”呢?

真正的并⾏执⾏多任务只能在多核CPU上实现,但是,由于任务数量远远多 于CPU的核⼼数量,所以,操作系统也会⾃动把很多任务轮流调度到每个核 ⼼上执⾏。

在这里插入图片描述

【4】并行 &&并发。

并行:多个计算机核心同时处理多个任务;多个任务之间是并行关系
并发:计算机同时处理多个任务,内核在多个任务之间不断切换,达到好像在同时处理的运行效果。此时多个任务实际为并发关系,如IO多路复用

二、多进程编程

【1】进程 VS 程序

编写完毕的代码,在没有运⾏的时候,称之为程序
正在运⾏着的代码,就成为进程
注意: 进程,除了包含代码以外,还有需要运⾏的环境等,所以和程序是有区别的

进程:程序在计算机中运行一次的过程,是一个动态的过程描述;占有CPU内存等计算机资源,具有一定的生命周期
程序:静态的可执行文件,占有磁盘,不占有计算机的运行资源;磁盘不属于计算机资源

备注:同一程序的不同执行过程,分配的计算机资源不同,属于不同的进程

【2】进程的创建流程

1、用户空间运行一个程序,发起进程创建
2、操作系统接受用户的请求,开启进程的创建
3、操作系统分配系统资源,确定进程状态
4、将创建好的进程提供给应用层使用

【3】进程的相关概念

1、cpu时间片
如果一个进程占有计算机核心,我们称之为该进程在CPU的时间片上。
多个任务实际上对CPU会进行争夺,一般由操作系统分配CPU时间片
2、进程控制块
PCB(Processing Control Block)
在操作系统中,进程创建后会自动产生一个空间存放进程信息,称之为进程控制块。
进程控制块包含进程的PID,memory 进程占有的内存位置,创建时间,用户,状态等
3、进程PID
进程在操作系统中的唯一编号,是一个大于0的整数,由系统自动分配
4、进程的特征
进程是操作系统分配计算机资源的最小单位
每个进程都有自己单独的虚拟内存空间,进程间的执行相互独立,互不影响
5、ps -aux 查看进程的信息

   D    等待态    不可中断等待
    S    等待态    可中断等待
    T    等待态    暂停状态
    R    运行态    就绪态,运行态
    Z    僵尸态    进程已经结束,但仍存在一些问题解决

    +    前台进程
    <    高优先级
    N    低优先级
    l    有进程链接
    s    会话组组长

6、进程的优先级
优先级决定了一个进程的执行权限和占有资源的优先程度
top:动态的查看进程的优先级
-20 —19,其中-20的优先级最高
nice:指定优先级运行程序

7、父子进程 :
除了初始化进程每个进程都有一个父进程,也有可能有0个或多个进程,
pstree: 查看进程树
ps -ajx :查看父进程的PID

【4】进程的状态

1、三态:
就绪:进程具备执行条件,等待系统分配CPU
执行:进程占有的CPU时间片,处于运行状态
等待:进程暂时不具备运行条件,需要阻塞等待
2、五态(增加新建态和终止态)
新建:创建一个新的进程,获取资源的过程
终止:进程结束,释放资源的过程
在这里插入图片描述

【5】进程的相关函数

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

2、os.getppid()
功能:获取父进程的pid号
返回值:返回PID号
3、os._exit(status)
功能:退出进程
返回值:使用整数标识进程的退出状态,并通过人为赋予相应的含义
4、sys.exit([status])
功能:退出状态
返回值:默认为0,如果是整数,则表示退出状态;如果是字符串,则表示在退出时打印内容。
备注:sys.exit()可以通过捕获SystemExit异常阻止退出

【6】孤儿进程

父进程先于子进程退出,此时子进程就会成为孤儿进程。
孤儿进程会被系统指定的进程收养,即系统进程会成为该孤儿进程新的父进程,处理孤儿进程的退出状态;
孤儿进程一定不会成为僵尸进程。

【7】僵尸进程

子进程先于父进程退出,并且父进程没有处理子进程退出状态,此时子进程成为僵尸进程;
僵尸进程已经结束,但是会滞留部分PCB信息在内存,大量的僵尸会消耗系统资源,应该尽量避免僵尸进程的产生。

三、创建子进程

【1】os.fork()

Python的os模块封装了常⻅的系统调⽤,其中就包括fork,可以在Python程 序中轻松创建⼦进程:

在这里插入图片描述

Linux 操作系统提供了一个 fork() 函数用来创建子进程,这个函数很特殊,调用一次,返回两次,
因为操作系统是将当前的进程(父进程)复制了一份(子进程),然后分别在父进程和子进程内返回。
子进程永远返回0,而父进程返回子进程的 PID。
我们可以通过判断返回值是不是 0 来判断当前是在父进程还是子进程中执行。

执⾏到os.fork()时,操作系统会创建⼀个新的进程复制⽗进程的所有信息到⼦进程中
普通的函数调⽤,调⽤⼀次,返回⼀次,但是fork()调⽤⼀次,返回两次
⽗进程和⼦进程都会从fork()函数中得到⼀个返回值,⼦进程返回是0,⽽⽗进程中返回⼦进程的 id号

import os
import time

# 定义一个全局变量money
money = 100
print("当前进程的pid:", os.getpid())
print("当前进程的父进程pid:", os.getppid())
time.sleep(15)


p = os.fork()
# 子进程返回的是0
if p == 0:
    money = 200
    print("子进程返回的信息, money=%d" %(money))
# 父进程返回的是子进程的pid
else:
    print("创建子进程%s, 父进程是%d" %(p,  os.getppid()))
    print(money)

在这里插入图片描述

【2】多进程修改全局变量

多进程中,每个进程中所有数据(包括全局变量)都各有拥有⼀份,互 不影响

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

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

(2)父进程处理子进程的状态
pid , status =os.wait()
功能:在父进程中阻塞等待处理子进程的退出
返回值:pid退出的子进程PID号
status 子进程的退出状态默认为0

pid,status = os.waitpid(pid,option)
功能,返回值:同wait
参数:
pid -1 表示任意子进程退出
>0 指定PID号的子进程退出
option 0 表示阻塞等待
WNOHANG 表示非阻塞
waitpid(-1,0) 等价于wait()

四、multiprocessing模块

Windows没有fork调⽤,由于Python是跨平台的, multiprocessing模块就是跨平台版本的多进程模块。
multiprocessing模块提供了⼀个Process类来代表⼀个进程对象。

【0】使用multiprocessing模块

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

【1】创建进程对象

Process([group [, target [, name [, args [, kwargs]]]]])
target:表示这个进程实例所调⽤对象;
args:表示调⽤对象的位置参数元组;
kwargs:表示调⽤对象的关键字参数字典;
name:为当前进程实例的别名;
group:⼤多数情况下⽤不到;
P.start()
启动进程,此时进程被创建,并自动执行进程函数
P.join([timeout])
功能:阻塞等待回收响应的进程
参数 :超时时间

【2】Process类常⽤⽅法:

is_alive(): 判断进程实例是否还在执⾏;
join([timeout]): 是否等待进程实例执⾏结束,或等待多少秒;
start(): 启动进程实例(创建⼦进程);
run(): 如果没有给定target参数,对这个对象调⽤start()⽅法时,
就将执 ⾏对象中的run()⽅法;
terminate(): 不管任务是否完成,⽴即终⽌;

【3】Process类常⽤属性:

name:当前进程实例别名,默认Process-N,N为从1开始计数;
pid:当前进程实例的PID值;
p.daemon 默认值为False,父进程退出,不会影响子进程的运行
设置为True时,父进程退出,子进程也将退出
daemon 的设置必须在start前
如果设置daemon 为True,则不再使用join,在子进程退出前,会自动做处理
p.is_alive() 判断进程生命周期状态,返回True/False

【4】备注

1、multiprocessing创建进程是原来进程的子进程,创建后父子进程各自执行互不影响
2、子进程同样是复制父进程的空间,子进程对内容的修改不会影响到父进程的空间
3、join回收子进程,会有效的阻止僵尸进程的产生
4、通常使用multiprocessing创建进程,父进程只用作进程的创建和回收,不做其他工作
5、join()方法可以等待子进程结束后再继续往下运行(更准确地说,在当前位置阻塞主进程,带执行join()的进程结束后再继续执行主进程),通常用于进程间的同步。

【5】案例
from multiprocessing import Process
import os
import time


def run_proc(name):
    time.sleep(10)
    print('Run child process(子进程) %s (%s)...' % (name, os.getpid()))


def hello_world():
    # time.sleep(5)
    time.sleep(20)
    print('hello world!')
    print('Run child process(运行子进程) (%s)...' % (os.getpid()))


if __name__ == '__main__':
    print('Parent process(父进程) %s.' % os.getpid())
    p1 = Process(target=run_proc, args=('test',))
    p2 = Process(target=hello_world)
    print("Process will start.")
    p1.start()
    
    p2.start()
    p1.join()
    print('Process end.')

##id号有差错主要看程序运行流程
在这里插入图片描述
在这里插入图片描述
注意: p1.start() p2.start() p1.join() p2.join()
加入join()阻塞的位置不同,程序运行顺序会不同

# -*- coding:utf-8 -*-
from multiprocessing import Process
import os
import time


def run_proc(name):
    for i in range(2):
        print(i)
        print('【task1-%d】Run child process(子进程) %s (%s)...' % (i, name, os.getpid()))
        time.sleep(1)


def hello_world():
    # time.sleep(5)
    for i in range(3):

        print('hello world!')
        print('[task2-%d]Run child process(运行子进程) (%s)...' % (i, os.getpid()))
        time.sleep(3)

if __name__ == '__main__':
    print('Parent process(父进程) %s.' % os.getpid())
    p1 = Process(target=run_proc, args=('test',))
    p2 = Process(target=hello_world)
    print("Process will start.")
    p1.start()
    p2.start()
    p1.join()
    p2.join()
    print('Process end.')

(1)

 p1.start()
    p2.start()
    p1.join()
    p2.join()

在这里插入图片描述
(2)
p1.start() p1.join() p2.start() p2.join()

在这里插入图片描述

五、多进程编程

【1】方法一:实例对象
"""
Process([group [, target [, name [, args [, kwargs]]]]])
	target:表示这个进程实例所调⽤对象;
	args:表示调⽤对象的位置参数元组;
	kwargs:表示调⽤对象的关键字参数字典;
	name:为当前进程实例的别名;
	group:⼤多数情况下⽤不到;
Process类常⽤⽅法:
	is_alive():	判断进程实例是否还在执⾏;
	join([timeout]):	是否等待进程实例执⾏结束,或等待多少秒;
	start():		启动进程实例(创建⼦进程);
	run():		如果没有给定target参数,对这个对象调⽤start()⽅法时,
	           		就将执 ⾏对象中的run()⽅法;
	terminate():	不管任务是否完成,⽴即终⽌;

"""

from multiprocessing import Process
import time


def task1():
    print("正在听音乐")
    time.sleep(1)


def task2():
    print("正在编程......")
    time.sleep(0.5)


def no_multi():
    task1()
    task2()

#实例化对象

def use_multi():
    p1 = Process(target=task1)
    p2 = Process(target=task2)
    p1.start()
    p2.start()
    p1.join()
    p2.join()

    # p.join()  阻塞当前进程, 当p1.start()之后, p1就提示主进程,
    # 需要等待p1进程执行结束才能向下执行, 那么主进程就乖乖等着, 自然不会执行p2.start()
    # [process.join() for process in processes]

if __name__ == '__main__':
    # 主进程
    start_time= time.time()
    # no_multi()
    use_multi()
    end_time = time.time()
    print(end_time-start_time)
【2】方法二:创建子类
"""
创建子类, 继承的方式
"""
from multiprocessing import Process
import time


class MyProcess(Process):
    """
    创建自己的进程, 父类是Process
    """

    def __init__(self, music_name):
        super(MyProcess, self).__init__()
        self.music_name = music_name

    def run(self):
        """重写run方法, 内容是你要执行的任务"""

        print("听音乐%s" % (self.music_name))
        time.sleep(1)


# 开启进程: p.start()  ====== p.run()
if __name__ == '__main__':
    for i in range(10):
        p = MyProcess("音乐%d" % (i))
        p.start()

六、进程池

【1】为什么需要进程池Pool?
当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。

Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。
在这里插入图片描述

【2】背景

多进程编程能并行执行多个任务,具有效率高,创建方便,运行独立,不受其他进程影响,数据安全等特点;
但进程的创建和删除都需要消耗计算机的资源。如果有大量任务需要多进程完成,且可能需要频繁的创建和删除进程,将给计算机带来大量的资源消耗。

【3】原理

在进程池内运行一定数量的进程,通过这些进程完成进程池队列中的事件,直到事件执行完,减少进程的不断地创建删除过程。

【4】实现

1、创建进程池,在进程池中放入适当的进程
2、将事件加入到进程池队列
3、事件不断运行,直到所有事件运行完毕
4、关闭进程池,回收进程

【5】语句
from multiprocessing import Pool
pool =Pool(processes)  #创建进程池对象;参数:表示进程池中有多少进程
pool.apply_async(func,args,kwargs)  #将事件放入进程池对列
#参数:func 要执行的1事件
			args 给func 用元组传参 
			kwargs 给func用字典传参
			返回值:返回事件对象 通过get()方法获取事件函数返回值
pool.map(func,iter)  #将要完成的事件放入进程池
#参数:func 要完成的事件函数, iter 可迭代对象给func传值
#返回值:时间函数的返回值列表

pool.close()  #关闭进程池,不能在添加新的事件
pool.join()  #阻塞等待回收进程池
【6】方法一:apply_saync`
"""使用"apply_async()"需要自己创建列表,维护返回值'"""
from multiprocessing import Pool
from time import sleep,ctime

def event(message):
    sleep(2)
    print(ctime(), ':', message)
    return '&' + str(message) + '&'

print(ctime())
pool = Pool(4)
r_list = []  

for i in range(10):
    # apply效率非常低,进程池中同一时刻,只有一个进程在运行。
    # 程序在此处阻塞
    
    # element = pool.apply(event,(i,))
    # apply_async可以提升效率:进程池中的进程并行执行,一起返回,直到所有进程都执行完毕;
    # 程序在此处并不会阻塞
    element = pool.apply_async(event, (i,))
    print(element)
    r_list.append(element)

for e in r_list:
    # print(e)
    # apply_async返回事件对象,可通过get()方法获取事件函数返回值
    print(e.get())

print(ctime())

pool.close()
pool.join()

【7】方法二:map
"""    def map(self, func, iterable, chunksize=None):
        '''
        Apply `func` to each element in `iterable`, collecting the results
        in a list that is returned.
        '''
        return self._map_async(func, iterable, mapstar, chunksize).get()"""
'''方法二 使用进程池的map方法;返回值为列表,无需自己创建维护列表'''
from multiprocessing import Pool
from time import sleep, ctime


def event(message):
    sleep(1)
    print(ctime(), ':', message)
    return '&' + str(message) + '&'


pool = Pool(4)

print(ctime())
# 阻塞,返回值为列表
l = pool.map(event, range(10))
# 非阻塞,返回值为对象,使用get()方法获取列表,get()会阻塞
# l = pool.map_async(event, range(10))

print(l)
#print(l.get())

pool.close()
pool.join()

print(ctime())
【8】processPoolExecutor 进程池
import multiprocessing

def job(id):
    print("Start %d...."%(id))
    print("End %d...."%(id))
# #创建进程池对象
# pool =multiprocessing.Pool(processes=4)
#
# #给进程池分配任务
# for i in range(10):
#     pool.apply_async(job,args=(i+1,))
# pool.close()
#
# #等待所有的子进程执行结束,关闭进程池对象
# pool.join()
#
# print("所有任务执行结束...")

from concurrent.futures import ProcessPoolExecutor

pool = ProcessPoolExecutor(max_workers=4)
pool.map(job,range(10))
【9】案例:多进程与进程池判断素数
def is_prime(num):
    if num ==1:
        return False
    for i in range(2,num):
        if num % i ==0:
            return False
    else:
        return True

def task(num):
    if is_prime(num):
        print("%d是素数" %(num) )

from multiprocessing import Process

def use_multi():
    ps =[]
    for num in range(1,10000):
        #实例化子进程对象
        p = Process(target=task,args=(num,))
        #开启子进程
        p.start()
        #储存所有的子进程对象
        ps.append(p)

    #阻塞子进程,等待所有的子进程执行结束,在执行主进程
    [p.join() for p in ps]

def no_multi():
    for num in range(1,50000):
        task(num)

def use_pool():
    from multiprocessing import Pool
    from multiprocessing import cpu_count
    p = Pool(cpu_count())
    """    def map(self, func, iterable, chunksize=None):
        '''
        Apply `func` to each element in `iterable`, collecting the results
        in a list that is returned.
        '''
        return self._map_async(func, iterable, mapstar, chunksize).get()"""
    p.map(task,list(range(1,50000)))
    #关闭进程池
    p.close()
    #阻塞,等待所有的子进程执行结束,在执行主进程
    p.join()

if __name__ == '__main__':
    import time
    start_time =time.time()
    no_multi()
    # #实验数据1000->0.0049896240234375  10000->0.32386064529418945  50000->6.718240261077881

    #use_multi()
    #1000->1.1049039363861084  10000->创建太多子进程 OSError: [Errno 24] Too many open files

    #use_pool()
    # #1000->0.11014962196350098 10000->0.10912609100341797  50000->2.164581537246704
    end_time = time.time()

    print(end_time -start_time)

由此可见当处理数据量越大的时候,进程池的处理速度性能较好。

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值