python并发编程之多进程

python并发编程之多进程

在讲解python多进程之前,我们先引出一个话题,什么话题呢?“多任务”,那么什么是多任务呢,它是干什么的呢,来让我们一起来看看吧!

一、多任务

1、什么是多任务?

​ (1)在python中所了解到的函数执行,遇到多个函数调用,它的执行方式是按照函数调用的顺序依次挨个执行,基本上不可能出现多个函数同时执行的现象。

​ (2)那么,也就是说我们所写的程序都是但任务的,一个函数或者方法执行完毕,另一个函数或者方法才能正常执行。如果想要多个函数或方法同时调用执行,需要使用多任务。

​ (2)那么为什么要用多任务的方式去同时执行多个函数呢?因为多任务的最大好处就是:充分利用cpu,提高我i们程序的执行效率

2、多任务的概念?

多任务是指在同一时间内,去执行多个任务,一个任务可以视之为为一个应用程序。目前的操作系统都是多任务的操作系统,可以同时运行多个软件。

3、多任务的执行方式

(1)并发

所谓并发,就是指:在同一时间内去交替执行多个任务

例如:

​ 对于单核cpu处理多任务:操作系统会轮流让各个软件交替执行。假如,我们开了3款软件,qq、微信、网易云音乐。那面操作系统会让qq执行一段时间(假如是0.01秒),然后切换到微信,再去执行一段时间,然后继续切换到网易云音乐,再去执行一段时间……反复执行下去。表面上看这些软件都在同时运行,但实则是轮流挨个执行,因为CPU的计算速度太快,我们根本有肉眼无法看到。

(2)并行

所谓并行,是指:在同一时间内去给CPU每个内核挨个分配一个任务,当任务超过内核数,那么将和并发的处理机制一样,再给每个内核配发任务,也就是说 并行要在并发的基础上进行

例如:

​ 对于多核CPU处理多任务,操作系统会给CPU的每个内核去分配一个执行的软件,当开的任务数超过内核数,那么处理机制就会和并发的一样,也就是说每个内核都会去执行多个不同的任务。多个内核才是真真正正的多个软件同时运行,注意:多核CPU是以并行的方式去执行多任务,始终有多个软件一起执行。

二、进程

1、什么是进程

​ 在python中,想要是实现多任务,可以使用进程来完成,使用进程是完成多任务的一种方式

2、进程的概念

​ 在计算机中,正在运行的程序或者软件,就是一个进程。进程是操作系统进行资源分配的最基本单位,也就是说每开启一个软件(进程),操作系统都会给其分配一定的运行资源(内存资源),保证其正常运行。

例如:

​ 生活中的工厂(计算机),每一个车间就是一个进程,工厂提供资源(地方、电力设备……等),真正能够干活是车间的员工,员工可以理解为线程。

注意:

一个程序的运行至少有一个进程,一个进程默认有一个线程,进程里边可以创建多个子进程,线程是依附在进程里边的,没有进程就没有线程。

3、进程的处理方式

(1)单进程

​ 默认程序运行都需要去创建一个进程,一个python文件的运行,就是开启一个进程去处理,该python文件就被称为主进程。

进程中的场景:主线程去执行代码

(2)多进程

​ 一个python文件的运行,占用一个进程去处理,那么假如要同时运行两个python文件,那么就要给第二个python文件去开启一个进程去处理

​ 多进程可以实现多任务,每个进程就好比一个独立的车间,每个车间都各自在运行,也就是说每个进程也各自在运行,执行各自的任务。

三、进程的状态

​ 在了解怎么使用多进程之前,我们首先了解一下进程的三个状态。根据进程执行时的间断性,决定了进程可能有多种状态,但事实上,运行中的进程具有以下三种基本状态:

1、就绪状态:

​ 就绪状态,英文名Ready,进程已获得除处理器外的所有资源,只是在等待分配处理器资源,只要分配了处理器资源,进程就可以执行,例如我们的python文件,在不运行时就是处于就绪状态,以及主进程中的子进程不开启就是处于就绪状态。

2、运行状态

​ 运行状态,英文名称Running,进程占用处理器资源,处于此状态的进程的数目小于等于处理器的数目,在没有其他进程可执行时(如所有进程都进入阻塞状态),系统通常会自动执行系统的空闲进程

3、阻塞状态

​ 阻塞状态,英文名Blocked,系统由于进程等待某种条件(比如输入输出函数,进程的join方法),在满足条件之前无法继续执行。该事件发生前,即使把处理器资源分配给该进程,该进程也无法运行。

进程三态转换图:

在这里插入图片描述

四、多进程的使用

1、multiprocessing 模块介绍

​ python中的多线程无法利用多核优势,如果想要充分的使用多核CPU的资源,在python中大部分情况需要使用多进程。python中提供了 multipricessing 模块,该模块用于开启进程,并在子进程中执行我们定制的任务,比如函数。所有说要想使用多进程就必须导入 multiprocessing 模块。

# 导入multiprocessing模块
import multiprocessing
# 导入multiprocessing模块下的Process类
from multiprocessing import Process

2、Process 类的的介绍和使用

(1) Process 进程类的介绍
# 实例化进程
r1 = Process([group, target, name, args, kwargs])
# group :指定进程组,目前只能使用None
# target :执行的目标任务名称
# name :进程的名称
# args :以元组方式给执行的任务传递参数
# kwargs :以字典的方式给执行的任务传递参数
(2)Process 实例对象常用方法和属性

​ 1、start( ) : 表示启动创建的子进程

from multiprocessing import Process
import time

def run(name,num):
    start = time.time()
    for i in range(1,num):
        print(f"{name}跑了第{i}圈")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end-start))



def sing(name,num):
    start = time.time()
    time.time()
    for i in range(1,num):
        print(f"{name}唱了第{i}首歌")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end - start))

r1 = Process(group=None,target=run,name="run",args=("小明",5))
s1 = Process(group=None,target=sing,name="sing",kwargs={"name":"小花","num":5})
# 此时将同步执行r1和s1线程,或者说这两个函数
r1.start()
s1.start()

​ 2、join( ) : 表示等待子进程执行结束,相当于给该子进程做了一个单独的检测,当该子进程执行结束之后,主进程或其他进程才能继续往后执行

from multiprocessing import Process
import time

def run(name,num):
    start = time.time()
    for i in range(1,num):
        print(f"{name}跑了第{i}圈")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end-start))



def sing(name,num):
    start = time.time()
    time.time()
    for i in range(1,num):
        print(f"{name}唱了第{i}首歌")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end - start))

r1 = Process(group=None,target=run,name="run",args=("小明",5))
r1.start()
# 此时只有将r1进程全部运行完毕后才去执行主进程main,然后去执行子进程s1
r1.join()
s1 = Process(group=None,target=sing,name="sing",kwargs={"name":"小花","num":5})
s1.start()
print(f"run函数{r1.name}")
print(f"sing函数{s1.name}")

​ 3、terminate( ) :表示不管该子进程的任务是否完成,立即终止该子进程

from multiprocessing import Process
import time

def run(name,num):
    start = time.time()
    for i in range(1,num):
        print(f"{name}跑了第{i}圈")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end-start))



def sing(name,num):
    start = time.time()
    time.time()
    for i in range(1,num):
        print(f"{name}唱了第{i}首歌")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end - start))

r1 = Process(group=None,target=run,name="run",args=("小明",5))
r1.start()
# 此r1还没开始就被强制结束
r1.terminate()
s1 = Process(group=None,target=sing,name="sing",kwargs={"name":"小花","num":5})
s1.start()
print(f"run函数{r1.name}")
print(f"sing函数{s1.name}")

​ 4、name : 当前进程的别名,默认为 Process-N,N为从1开始的递增整数

​ **注意:**一般情况下主进程会体现运行然后等待其他子进程执行完毕后结束

from multiprocessing import Process
import time

def run(name,num):
    start = time.time()
    for i in range(1,num):
        print(f"{name}跑了第{i}圈")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end-start))



def sing(name,num):
    start = time.time()
    time.time()
    for i in range(1,num):
        print(f"{name}唱了第{i}首歌")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end - start))

r1 = Process(group=None,target=run,name="run",args=("小明",5))
r1.start()
s1 = Process(group=None,target=sing,name="sing",kwargs={"name":"小花","num":5})
s1.start()
print(f"run函数{r1.name}")
print(f"sing函数{s1.name}")

3、获取进程编号

(1)获取当前进程的编号

​ 方法:使用os模块下的getpid( )方法

os.getpid()
(2)获取当前进程的父进程的编号

​ 方法:使用os模块下的 getppid( ) 方法

os.getppid()

例子:

from multiprocessing import Process
import time,os

def run(name,num):
    print(f"run进程的id:{os.getpid()}")
    print(f"main进程的id:{os.getppid()}")
    start = time.time()
    for i in range(1,num):
        print(f"{name}跑了第{i}圈")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end-start))



def sing(name,num):
    print(f"sing进程的id:{os.getpid()}")
    print(f"main进程的id:{os.getppid()}")
    start = time.time()
    time.time()
    for i in range(1,num):
        print(f"{name}唱了第{i}首歌")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end - start))

print(f"main进程的id:{os.getpid()}")
r1 = Process(group=None,target=run,name="run",args=("小明",5))
r1.start()
s1 = Process(group=None,target=sing,name="sing",kwargs={"name":"小花","num":5})
s1.start()

至此,我们发现一个规律:

​ 所有的子进程都来自于主进程,即 main ,因此只要得知一个程序中的主进程编号,那么该程序中的子进程编号按照主进程编号为起始值加一计算

(3)获取当前进程的详细信息
# multiprocessing.current.process()
# 说明:用于判断该进程使子进程还是主进程,并且获取该进程的 别名和该进程的父进程编号
# 例如:
# 子进程  别名:run   父进程编号  开启状态
<Process name='run' parent=5548 started>
# 主进程       名称     主进程没有父进程,所有没有编号  进程处于开启状态    
<_MainProcess name='MainProcess' parent=None started>
(4)获取进程编号的作用

​ 获取进程编号的目的主要数为了验证 主进程 和 子进程 之间的关系,可以得知子进程是由哪个主进程创建出来的

4、主进程创建守护进程

​ 守护进程是Process类的实例对象所使用的布尔类型的属性,守护进程标志:Daemon属性

​ 当 子进程实例对象.Daemon = True 时表示该子进程被主进程守护,那么生命周期也在主进程执行的范围内,当主进程执行完毕,守护子进程被干掉,但是此时主进程不一定退出,因为有时候主进程还会等待非守护进程的执行。

import time,os,multiprocessing

def run(name,num):
    print(f"run进程的id:{os.getpid()}")
    print(f"main进程的id:{os.getppid()}")
    print(multiprocessing.current_process())
    start = time.time()
    for i in range(1,num):
        print(f"{name}跑了第{i}圈")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end-start))
    
print(f"main进程的id:{os.getpid()}")
r1 = multiprocessing.Process(group=None,target=run,name="run",args=("小明",5))
# 守护进程
r1.daemon = True
r1.start()
print(multiprocessing.current_process())

总结:

​ (1)被主进程守护的子进程,在主进程执行完毕后就被干掉

​ (2)守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children

​ (3)要在开启子进程之前,设置当前子进程被主进程守护

​ (3)如果子主进程内有守护子进程,并且还要非守护子进程,那么当主进程执行完毕后会等待非守护进程执行完毕后才结束,而守护子进程在主进程执行完毕后就被敢干掉。

5、杀死字进程

# 在python中使用os模块下的 kill 方法来杀死进程
os.kill(__pid, __signal)
# 参数:
# 1、__pid :该子进程的编号 os.get.getpid()
# 2、__signal :接收一个int类型的编号,一般情况下为9,表示杀死进程,即强制结束该进程

__signal参数:信号编码:

在这里插入图片描述

示例:

import time,os,multiprocessing

def run(name,num):
    start = time.time()
    for i in range(1,num):
        if i == 4:
            # 当i=4的时候,r1进程就被杀死,下来只执行s1进程,主进程等待s1进程执行完毕后退出
            os.kill(os.getpid(), 9)
        print(f"{name}跑了第{i}圈")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end-start))



def sing(name,num):
    start = time.time()
    time.time()
    for i in range(1,num):
        print(f"{name}唱了第{i}首歌")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end - start))


r1 = multiprocessing.Process(group=None,target=run,name="run",args=("小明",6))
s1 = multiprocessing.Process(group=None,target=sing,name="sing",kwargs={"name":"小花","num":6})
s1.start()
r1.start()

6、进程的特征

(1)进程之间不共享全局变量

演示:

import time,os,multiprocessing

def run(num):
    start = time.time()
    for i in range(1,num):
        # 添加元素
        my_list.append(i)
        time.sleep(1)
    end = time.time()
    print(f"r1进程中的元素:{my_list}")
    print("{:.0f}s".format(end-start))



def sing(num):
    start = time.time()
    time.time()
    for i in range(num):
        # 添加元素
        my_list.append(i)
        time.sleep(1)
    end = time.time()
    print(f"s1进程中的元素:{my_list}")
    print("{:.0f}s".format(end - start))


my_list = list()
r1 = multiprocessing.Process(group=None,target=run,name="run",args=(6,))
s1 = multiprocessing.Process(group=None,target=sing,name="sing",kwargs={"num":4})
s1.start()
r1.start()
print(f"主进程中的元素:{my_list}")

输出结果:

主进程中的元素:[]
s1进程中的元素:[0, 1, 2, 3]
4s
r1进程中的元素:[1, 2, 3, 4, 5]
5s

试想一下,为什么他们之间的元素都不一样,并且不是同一个列表呢?

结论:

​ 当一个进程对全局变量中的数据进行修改,对于其他进程而言,不会造成任何影响,可以理解为每个进程拿的都是最初的全局变量,或者可以理解为全局变量就是所谓的资源,当创建一个进程,则系统会直接给这个进程复制一个全局变量,针对于这个全局变量而言,再进程之间都是相互独立存在的,之间没有任何的关系。

关系图:

在这里插入图片描述

(2)主进程会等待所有子进程结束以后再结束

​ 1、所有子进程结束主进程才会结束,那么也就是说当子进程没有执行完毕,你就结束主继承,那么是无法做到的

import time,os,multiprocessing

def run(num):
    start = time.time()
    for i in range(1,num):
        print("跑步")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end-start))



def sing(num):
    start = time.time()
    time.time()
    for i in range(num):
        # 添加元素
        print("唱歌")
        time.sleep(1)
    end = time.time()
    print("{:.0f}s".format(end - start))


my_list = list()
r1 = multiprocessing.Process(group=None,target=run,name="run",args=(6,))
s1 = multiprocessing.Process(group=None,target=sing,name="sing",kwargs={"num":4})
s1.start()
r1.start()
# 表示默认退出程序,在这里是没办法退出的,因为主进程要等待所有子进程结束才可以结束
exit()

​ 2、在主进程结束之间,手动结束了所有的子进程,那么程序的结束由主进程来控制

7、那么如何做到主程序结束则整个程序结束?

​ (1)在主进程结束之前保证所有子进程都使用 terminate( ) 方法去终止子进程

​ (2)在子进程开启之前,设置所有子进程被主进程守护,即所有子进程的属性 daemon = True

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

御弟謌謌

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值