多任务编程
先来看三个问题,对于多任务有个简单的认识:
1.首先什么是多任务呢?
就是操作系统可以同时运⾏多个任务。打个 ⽐⽅,你⼀边在⽤浏览器上⽹,⼀边在听MP3,⼀边在⽤Word赶作业,这就是多任务,⾄少同时有3个任务正在运⾏。还有很多任务悄悄地在后台同时运⾏着,只是桌⾯上没有显示⽽已。
2.那么单核CPU该如何实现“多任务”呢?
操作系统轮流让各个任务交替执⾏,每个任务执⾏0.01秒,这样反复执⾏下去。 表⾯上看,每个任务交替执⾏,但CPU的执⾏速度实在是太快了,感觉就像所有任务都在同时执⾏⼀样。正如下图所示:
3.多核CPU又是如何实现“多任务”的呢?
真正的并⾏执⾏多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核⼼数量,所以,操作系统也会⾃动把很多任务轮流调度到每个核⼼上执⾏。如下图所示:
多任务编程分为多进程编程、多线程编程和协程。
多进程编程
进程的创建
程序和进程它们有什么区别呢?
程序:编写完毕的代码,在没有运⾏的时候,称之为程序
进程:正在运⾏着的代码,就成为进程
注意:进程,除了包含代码以外,还有需要运⾏的环境等,所以和程序是有区别的。
进程的五状态模型
创建子进程
Python的os模块封装了常⻅的系统调⽤,其中就包括fork,可以在Python程序中轻松创建⼦进程:
在OS模块中,创建子进程时常用到的函数有:
- os.fork( )
- os.getpid( ) 获取当前进程的pid (process id)
- os.getppid( ) 获取当前进程的父进程pid (parent process id)
需要注意的是:
1.执⾏到os.fork()时,操作系统会创建⼀个新的进程复制⽗进程的所有信息到⼦进程中。
2.普通的函数调⽤,调⽤⼀次,返回⼀次,但是fork()调⽤⼀次,返回两次。
3.⽗进程和⼦进程都会从fork()函数中得到⼀个返回值,⼦进程返回是0,⽽⽗进程中返回⼦进程的 id号。
4.多进程中,每个进程中所有数据(包括全局变量)都各有拥有⼀份,所以遇到多进程修改全局变量的情况时互不影响。
下面我们用具体的代码,看一下如何创建子进程,以及通过输出结果观察上面需要注意的点,代码如下:
import os
import time
# 定义一个全局变量money
money = 100
print("当前进程的pid:", os.getpid())
print("当前进程的父进程pid:", os.getppid())
p = os.fork()
# 子进程返回的是0
if p == 0:
money = 200
print("子进程返回的信息, money=%d" %(money))
# 父进程返回的是子进程的pid
else:
print("创建子进程%s, 父进程是%d" %(p, os.getppid()))
print(money)
输出结果如下:
注意,fork函数,只在Unix/Linux/Mac上运⾏,windows不可以 。
多进程编程
Windows没有fork调⽤,由于Python是跨平台的,所以有一个 multiprocessing模块就是跨平台版本的多进程模块。multiprocessing模块提供了⼀个Process类来代表⼀个进程对象。
Process类需要传递的参数:
Process([group [, target [, name [, args [, kwargs]]]]])
- target:表示这个进程实例所调⽤对象;
- args:表示调⽤对象的位置参数元组;
- kwargs:表示调⽤对象的关键字参数字典;
- name:为当前进程实例的别名;
- group:⼤多数情况下⽤不到;
Process类常⽤⽅法:
- is_alive(): 判断进程实例是否还在执⾏;
- join([timeout]): 是否等待进程实例执⾏结束,或等待多少秒;
- start(): 启动进程实例(创建⼦进程);
- run(): 如果没有给定target参数,对这个对象调⽤start()⽅法时,就将执 ⾏对象中的run()⽅法;
- terminate(): 不管任务是否完成,⽴即终⽌;
Process类常⽤属性:
- name:当前进程实例别名,默认Process-N,N为从1开始计数;
- pid:当前进程实例的PID值;
下面通过代码看一下多任务编程的两个方法 :
多进程编程方法1: 实例化对象
通过实例化对象的方法创建子进程,具体代码如下:
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()
if __name__ == '__main__':
# 主进程
start_time= time.time()
no_multi()
end_time = time.time()
print(end_time-start_time)
start_time = time.time()
use_multi()
end_time = time.time()
print(end_time - start_time)
代码里面的no_multi函数是没有使用多进程的函数,use_multi函数是通过实例化方法创建了两个子进程,通过结果,可以看到多进程运行时间比没有使用多进程运行时间短,结果如下:
多进程编程方法2: 创建子类
通过创建子类,重写run方法,实现多进程编程,具体例子代码如下:
"""
创建子类, 继承的方式
"""
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()
输出结果如下:
进程池
为什么需要进程池Pool呢?
1.当被操作对象数目不大时,可以直接利用multiprocessing中的Process动态成生多个进程,十几个还好,但如果是上百个,上千个目标,手动的去限制进程数量却又太过繁琐,此时可以发挥进程池的功效。
2.Pool可以提供指定数量的进程供用户调用,当有新的请求提交到pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到规定最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来它。
它的工作原理如下图所示:
Python中的multiprocessing模块中的Pool就是进程池,下面通过具体例子写一个进程池的代码:
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
# 判断1-100000之间所有的素数
def use_mutli():
ps = []
# 不要开启太多进程, 创建子进程会耗费时间和空间(内存);
for num in range(1, 10000):
# 实例化子进程对象
p = Process(target=task, args=(num,))
# 开启子进程
p.start()
# 存储所有的子进程对象
ps.append(p)
# 阻塞子进程, 等待所有的子进程执行结束, 再执行主进程;
[p.join() for p in ps]
# 判断1-100000之间所有的素数
def no_mutli():
for num in range(1, 100000):
task(num)
def use_pool():
"""使用进程池"""
from multiprocessing import Pool
from multiprocessing import cpu_count # 4个
p = Pool(cpu_count())
p.map(task, list(range(1, 100000)))
p.close() # 关闭进程池
p.join() # 阻塞, 等待所有的子进程执行结束, 再执行主进程;
if __name__ == '__main__':
import time
start_time = time.time()
use_pool()
end_time = time.time()
print(end_time - start_time)
使用进程池运行程序,可以看到输出结果的运行时间:
当不使用进程池,也不是用多任务时,很明显运行时间会比较长一点:
由于使用多进程时,需要开启上万个进程,这样会更加浪费时间。
进程间通信
目的:
方式:
消息队列:
可以使⽤multiprocessing模块的Queue实现多进程之间的数据传递,Queue本身是⼀个消息列队程序。
下面看一下Queue有哪些方法?
- Queue.qsize(): 返回当前队列包含的消息数量;
- Queue.empty(): 如果队列为空,返回True,反之False ;
- Queue.full(): 如果队列满了,返回True,反之False;
- Queue.get([block[, timeout]]): 获取队列中的⼀条消息,然后将其从列队中移除,block默认值为True;
- Queue.get_nowait(): 相当Queue.get(False);
- Queue.put(item,[block[, timeout]]): 将item消息写⼊队列,block默认值 为True;
- Queue.put_nowait(item): 相当Queue.put(item, False)
下面用代码实现消息队列,代码如下:
from multiprocessing import Process, Queue
import time
class Producer(Process):
def __init__(self, queue):
super(Producer, self).__init__()
self.queue = queue
def run(self):
for i in range(10):
self.queue.put(i)
time.sleep(0.1)
print("传递消息,内容为%s" %(i))
class Consumer(Process):
def __init__(self, queue):
super(Consumer, self).__init__()
self.queue = queue
def run(self):
while True:
time.sleep(0.1)
recvDate = self.queue.get()
print("接收到另一进程传递的数据:%s" %(recvDate))
if __name__ == '__main__':
q = Queue()
p1 = Producer(q)
c1 = Consumer(q)
p1.start()
c1.start()
p1.join()
c1.join()
输出结果为: