在现实社会,我们经常需要一种场景,就是同时有多个事情需要执行,如在浏览网页的同时需要听音乐。比如说在跳舞的时候要唱歌。
同样的,在程序中我们也可能需要这种场景。如下面我们以同时听音乐和浏览网页为例。
def network():
while True:
print("正在上网~~~")
time.sleep(1)def sing():
while True:
print("正在听歌……")
time.sleep(1)
if __name__ == "__main__":
network()
sing()
执行代码,我们发现代码并没有按照我们的想法执行,而是,一直执行上网的代码,这样就无法完成我们想要的效果。
如果你想要同时听音乐和浏览网页两件事,就需要一个新的方式来完成,这个方式就是----多任务。
多任务是咋回事呢?
什么叫做多任务呢?简单的说,就是操作系统(OS)可以同时运行多个任务。如你可以一边浏览网页,一边听着歌,同时还可以使用画板画着画,这个就是多任务。其实我们的操作系统就是多任务,只是我们都没有注意罢了。
单核CPU中:
1、时间片轮换
2、优先级别调度
3、多核CPU中:
4、并发
5、并行
Fork子进程
编写完的代码,在没有运行的情况下,称之为程序。
正在运行的代码,就成为了进程。
进程,除了包含代码外,还需要运行环境等,所以和程序是存在区别的。
Python在os模块上封装了常用的系统调用,其中就包括了fork,我们可以很轻松地在Python代码中创建进程。
# 注意,下面代码只能在Linux、Mac系统上运行,window下不支持运行!!!
import osimport time# 使用os模块的fork方法可以创建一个新的进程
pid = os.fork()if pid == 0:
while True:
print("son process", pid) # 子进程代码
time.sleep(1)
else:
while True:
print("main process", pid) # 父进程代码
time.sleep(1)
Python中的fork() 函数可以获得系统中进程的PID ( Process ID ),返回0则为子进程,否则就是父进程,然后可以据此对运行中的进程进行操作;
但是强大的 fork() 函数在Windows版的Python中是无法使用的。。。只能在Linux系统中使用,比如 Ubuntu 15.04,Windows中获取父进程ID可以用 getpid()。
getpid、getppid
我们可以使用os模块的getpid来获取当前进程的进程编号,同样也可以使用os.getppid()方法案来获取当前进程的父进程编号。
import osimport time# 使用os模块的fork方法可以创建一个新的进程
pid = os.fork()
if pid == 0:
while True:
print("我是子线程,我的编号是%s,我的父进程编号是%s(os.getpid(),os.getppid()))
time.sleep(1)
else:
while True:
print("我是父线程,我的编号是%s,我的子进程编号是%s" %(os.getpid(), pid))
time.sleep(1)
使用os模块中的getpid()方法,可以得到当前线程的编号,使用getppid()方法可以得到该线程的父级编号。
多进程修改全局变量多进程修改全局变量
import os
import time
# 使用os模块的fork方法可以创建一个新的进程
pid = os.fork()
if pid == 0:
while True:
print("我是子线程,我的编号是%s,我的父进程编号是%s" %(os.getpid(),os.getppid()))
time.sleep(1)
else:
while True:
print("我是父线程,我的编号是%s,我的子进程编号是%s" %(os.getpid(), pid))
time.sleep(1)
使用os模块中的getpid()方法,可以得到当前线程的编号,使用getppid()方法可以得到该线程的父级编号。
多进程修改全局变量
import os
# 定义一个全局变量
num = 0
pid = os.fork()
if pid == 0:
# 子进程
num += 1
print("子进程~~~~~~",num)
else:
#父进程
num += 1
print("父进程~~~~~~", num)
输出结果如下
父进程~~~~~~ 1
子进程~~~~~~ 1
由此可见,进程间是无法共享数据的。
注意:多个进程间,每个进程的所有数据(包括全局变量)都是各自拥有一份的,互不影响。
完成最开始的任务
import os
import time
def network():
for x in range(5):
print("正在上网~~~")
time.sleep(1)
def singe():
for x in range(5):
print("正在听歌……")
time.sleep(1)
pid = os.fork()
if pid == 0:
network()
else:
singe()
print("程序结束~~~")
多个fork问题
上面的所有程序中,我们使用了fork函数,生成了两个进程(一个主进程、一个子进程),但是如果我们在程序中需要多个进程呢?如两次调用fork函数,会生成三个进程吗?
import os,time
res = os.fork()
if res == 0:
print("一个子线程")
else:
print("主线程")
ret = os.fork()
if ret == 0:
print("第三个线程")
else:
print("第四个线程")
结果如下
主线程
第四个线程
第三个线程
一个子线程
第三个线程
第四个线程
我们发现,主线程和子线程各执行了一次,但是第三个和第四个都执行了两次,为什么了,看下面的图。
1.png
主进程和子进程的执行顺序
其实通过上面的代码,我们已经发现了,主进程和子进程的执行顺序是没有规律的,这个不受程序员的控制,有操作系统的调度算法来控制。
multiprocessing
前面我们使用os.fork()方法实现了多进程,但是这种方案只能在Linux下运行,window环境下是无法运行的,那么有没有可以实现在任何平台都能运行的多进程了,有!Python为大家提供了multiprocessing模块用来实现多进程。
函数实现方式
from multiprocessing import Process
import os
def run():
print("这个是一个独立的进程",os.getpid(),os.getppid())
if __name__ == "__main__":
print("代码开始运行……")
task = Process(target=run)
task.start() # 启动进程
print("代码运行结束……",os.getpid())
结果如下
2.png
我们发现主进程结束后,子进程才结束,说明我们确实开辟了一个独立的进程。
from multiprocessing import Process
import os
def run(msg):
for x in range(5):
print("这个是一个独立的进程",os.getpid(),os.getppid())
print("传递的参数是:",msg)
else:
print("子进程结束了……")
if __name__ == "__main__":
print("代码开始运行……")
# target表示独立进程的方法
#args表示要传递的参数,注意:args的类型是元组,也支持列表list
task = Process(target=run,args=("这个是参数",))
task.start() # 启动进程
print("代码运行结束……",os.getpid())
args为调用的子进程的函数的参数,注意类型是一个元组。
from multiprocessing import Process
import os
def run(msg):
for x in range(5):
print("这个是一个独立的进程",os.getpid(),os.getppid())
print("传递的参数是:",msg)
else:
print("子进程结束了……")
if __name__ == "__main__":
print("代码开始运行……")
# target表示独立进程的方法
#args表示要传递的参数,注意:args的类型是元组
task1 = Process(target=run,args=("这个是参数1",))
task1.start() # 启动进程
task2 = Process(target=run, args=("这个是参数2",))
task2.start() # 启动进程
task3 = Process(target=run, args=("这个是参数3",))
task3.start() # 启动进程
print("代码运行结束……",os.getpid())
多个进程启动还是一样,执行的顺序同样不可控。
(主进程等待子进程版):
join方法表示只有子进程执行完成后,主进程才能结束。主进程会等待子进程完成后,自身才会执行完成。
from multiprocessing import Process
import os,time
def run(msg):
print("子进程开始执行了……")
time.sleep(3)
print("子进程执行end了……")
if __name__ == "__main__":
print("代码开始运行……")
task1 = Process(target=run,args=("这个是参数1",))
print(task1.is_alive()) # is_alive()方法判断进程是否结束
task1.join() # 表示这个子进程执行完成,主进程才能继续向下执行
print(task1.is_alive())
print("代码运行结束……",os.getpid())
常用方法
from multiprocessing import Process
import os,time
def run(msg):
print("子进程开始执行了……")
time.sleep(3)
print("子进程执行end了……")
if __name__ == "__main__":
print("代码开始运行……")
# name表示我们认为的为这个子进程取个名称,
# 如果不写,默认是Process-n n从1开始
task1 = Process(target=run,args=("这个是参数1",),name="liujianhong")
task1.start() # 启动进程
print(task1.is_alive()) # is_alive()方法判断进程是否结束
task1.join() # 表示这个子进程执行完成,主进程才能继续向下执行
print(task1.name) # 得到子进程的名称
task1.terminate() # 强制结束进程
print(task1.is_alive())
print("代码运行结束……",os.getpid())
多个进程使用不同的方法版
from multiprocessing import Process
import os,time
def run(msg):
print("子进程1开始执行了……")
time.sleep(3)
print("子进程1执行end了……")
def run2(msg):
print("子进程2开始执行了……")
time.sleep(3)
print("子进程2执行end了……")
if __name__ == "__main__":
print("代码开始运行……")
task1 = Process(target=run,args=("这个是参数1",))
task1.start() # 启动进程
task2 = Process(target=run2, args=("这个是参数2",))
task2.start() # 启动进程
print("代码运行结束……",os.getpid())
类实现方式
在Python中,很多的方案都提供了函数和类两种实现方式,如:装饰器、自定义元类。同样多进程也有两种实现,前面我们已经看了使用函数实现的方式,下面我们使用类来实现以下呗。
进程类的实现非常的简单,只要继承了Process类就ok了,重新该类的run方法,run方法里面的代码,就是我们需要的子进程代码。
from multiprocessing import Process
import time
class MyProcess(Process):
# 重写run方法即可
def run(self):
print("一个子进程开始运行了")
time.sleep(1)
print("一个子进程开始运行结束了")
if __name__ == '__main__':
task = MyProcess()
task.start()
print("主进程结束了……")
在进程类的实现中如果想要初始化一些前面我们提到过的参数,如进程名称等,可以使用init借助父类来完成。
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self,name):
super().__init__(name=name)
# 重写run方法即可
def run(self):
print("一个子进程开始运行了")
time.sleep(1)
print("一个子进程开始运行结束了")
if __name__ == '__main__':
task = MyProcess("刘建宏")
task.start()
print(task.name)
print("主进程结束了……")
进程池Pool
当我们需要的进程数量不多的时候,我们可以使用multiprocessing的Process类来创建进程。但是如果我们需要的进程特别多的时候,手动创建工作量太大了,所以Python也为我们提供了Pool(池)的方式来创建大量进程。
from multiprocessing import Pool
import os,time
def run(msg):
print("开始一个子线程运行了……")
time.sleep(1)
print("开始一个子线程运行结束了……")
if __name__ == "__main__":
pool = Pool(3) # 表示初始化一个进程池,最大进程数为5
for x in range(10):
pool.apply_async(run, args=("hello pool",))
print("------start----")
pool.close() # 关闭池
pool.join() # 等待所有的子进程完成,必须放在close后面
print("-------end------")
注意:一般我们使用apply_async这个方法,表示非阻塞的运行,一旦使用了apply方法表示阻塞式执行任务,此时就是单任务执行了(一般不会使用,特殊场景才会使用)