进程
多任务原理
多任务的由来(进程 线程的发展):
1, 早期的计算机是没有操作系统的,只有输入,计算,输出。手工输入速度远低于计算机的计算速度。
2, 于是出现了批处理操作系统,通过纸带,磁带等工具预先写入指令,形成一个指令清单(即任务)交给计算机处理。但批处理系统的缺点是只能有一个任务,而且当计算机在进行I/O处理时,CPU是空闲的。
3, 世人发明了进程,一个进程就代表一个任务,多个进程通过分时操作能让用户认为并行操作多任务,进程间的资源是独立单元,但是可以通过介质进行通信。缺点:进程内只进行串行处理,无法很好地分工合作提高处理效率。
4, 于是就有了操作系统调度的最小单元-线程,线程能够使进程内的子任务能够共享进程内的资源,并并行工作,大大提高操作系统的性能。
区别:
线程是任务调度的最小单元,共用进程内的资源。
进程是资源分配的最小单元,与其他进程资源互相独立。
现代操作系统(Windows、Mac OS X、Linux、UNIX等)都支持“多任务”。
什么叫多任务???
操作系统同时可以运行多个任务。
思考:为什么会需要多任务?
单核CPU实现多任务原理:操作系统轮流让各个任务交替执行,QQ执行2us,切换到微信,在执行2us,再切换到陌陌,执行2us……。表面是看,每个任务反复执行下去,但是CPU调度执行速度太快了,导致我们感觉就行所有任务都在同时执行一样。
多核CPU实现多任务原理:真正的并行执行多任务只能在多核CPU上实现,但是由于任务数量远远多于CPU的核心数量,所以,操作系统也会自动把很多任务轮流调度到每个核心上执行。
并发:看上去一起执行,任务数多于CPU核心数。
并行:真正一起执行,任务数小于等于CPU核心数。
实现多任务的方式:
1、多进程模式
2、多线程模式
3、协程模式
4、多进程+多线程模式
进程
对于操作系统而言,一个任务就是一个进程。
进程是系统中程序执行和资源分配的基本单位。每个进程都有自己的数据段、代码段、和堆栈段。
先来体会一下单任务的场景:
from time import sleep
# 任务一
def run():
while True:
print('hot day')
sleep(1.2)
if __name__ == '__main__':
# 任务二
while True:
print('good day')
sleep(1)
# 只有上面的while循环结束才会执行到run
run()
如何让上面代码中的run方法执行呢。使用多进程。
python中多进程的库为multiprocessing.这是一个垮平台版本的多进程模块,提供了一个Process类来代表一个进程对象。
使用最简单的多进程实现上面的多任务场景:
from multiprocessing import Process
from time import sleep
def run():
while True:
print('hot day')
sleep(1.2)
if __name__ == '__main__':
print('主(父)进程启动')
# 创建子进程
p = Process(target=run)
# 启动进程
p.start()
while True:
print('good day')
sleep(1)
如果run方法需要接收参数,可以通过args来传递:
p = p = Process(target=run, args=('hot',))
注意args是位置参数,传递位置参数时,必须按照参数位置来传递。如果需要传递关键词参数使用 kwargs.
每个进程都有一个唯一的id号,我们一般称之为pid。
在python代码中获取进程id号使用:os.getpid()。
获取当前进程的父进程ip使用:os.getppid().多出来的p表示parent。
Process常见方法和属性
属性:
字段 | 说明 |
---|---|
daemon | 是否为守护进程, 父进程终止后自动终止,且自己不能产生新进程,必须在start()之前设置 |
name | 进程的名字,自定义 |
exitcode | 进程的退出状态码(无法自己设置) |
pid | 每个进程有唯一的PID编号(无法自己设置)。 |
方法:
方法名 | 说明 |
---|---|
is_alive() | 返回进程是否在运行 |
join([timeout]) | 阻塞(se)当前上下文环境,直到调用此方法的进程终止或者到达指定timeout |
start() | 启动进程,等待CPU调度 |
terminate() | 不管任务是否完成,立即停止该进程 |
run() | start()调用该方法,当实例进程没有传入target参数,start()将执行默认的run()方法 |
另一种创建进程的方式: 派生Process子类
使用Process子类的方式创建进程:
from multiprocessing import Process
from time import sleep
class MyProcess(Process):
# 定义init方法方便传参数进来
def __init__(self, id, *args, **kwargs):
# 调用父类的init方法
super().__init__(*args, **kwargs)
# 赋值
self.id = id
# 重写run方法
def run(self):
print('子进程%d开始运行' % self.id)
print(self)
sleep(2)
print('子进程%d结束运行' % self.id)
if __name__ == '__main__':
print('父进程开始运行')
# 创建子进程
for i in range(4):
p = MyProcess(name='子进程'+str(i),id=i)
p.start()
print('父进程结束')
父子进程的先后顺序
通过一个例子来体会父子进程的执行顺序:
from multiprocessing import Process
from time import sleep
def run():
print('子进程启动')
sleep(3)
print('子进程结束')
if __name__ == '__main__':
print('父进程启动')
p = Process(target=run)
p.start()
print('父进程结束')
执行结果为:
父进程启动
父进程结束
子进程启动
子进程结束
可以发现父进程并不等待子进程的结束而结束,如果需要实现这一下效果,可以使用p.join()。
如果想给这个等待加一个期限,可以设置一个timeout时间,单位为秒。
全局变量在多个进程中不能共享
通过代码来说明这一现象:
from multiprocessing import Process
from time import sleep
# 定义全局变量
num = 100
# 子进程
def run():
print('开始执行子进程')
# 引入全局变量
global num
# 修改全局变量
num += 1
print(num)
print('子进程结束')
if __name__ == '__main__':
print('父进程开始')
p = Process(target=run)
p.start()
print('父进程结束--%d'%num)
执行结果:
父进程开始
父进程结束--100
开始执行子进程
101
子进程结束
发现子进程中修改的全局变量在父进程中依然还是100.
出现这种现象的原因是子进程不和父进程共享全局变量,而是单独拷贝了一份全局变量。
不光父子进程不共享全局变量,兄弟进程也不共享全局变量。请写程序验证这一点。
使用进程池创建大量进程
先来看一个例子:
# coding: UTF-8
from multiprocessing import Process, Pool
import os, time, random
def run(name):
print('子进程%d启动--%s' % (name, os.getpid()))
start = time.time()
time.sleep(random.choice([1, 2, 3]))
end = time.time()
print('子进程%d结束--%s--耗时%.2f' % (name, os.getpid(), end-start))
if __name__ == '__main__':
print('父进程启动')
# 创建进程池
# 4表示同时可以执行的进程数量,不写默认是电脑的cpu内核数
pp = Pool(4)
for i in range(5):
pp.apply_async(run, args=(i,))
# 关闭进程池,调用close之后不能再继续添加新的进程。
pp.close()
# 等进程池中的进程都完成再结束进程池, 注意必须在调用close之后调用join
pp.join()
print('父进程结束')
使用进程池,需要创建进程池实例对象,指明这个进程池中可以放多少个进程。使用apply_async()方法在进程池中创建子进程。添加完子进程后,需要使用close方法关闭进程,关闭之后无法往进程池中添加新的子进程。使用进程池的join方法必须在调用close之后。
练习:使用进程池,把大量文件从一个目录拷贝到另一个目录。首先实现单进程方式,再用进程池实现多进程方式。对比两者的效率。
进程间通信
进程间通信使用Queue对象。
先来看一个例子:
from multiprocessing import Process, Queue
import time, random, os
def write(q):
print('开始子进程%s' % (os.getpid()))
for value in 'abdcef':
print(time.ctime(), 'put %s to queue' % value)
q.put(value)
time.sleep(random.random())
def read(q):
while True:
value = q.get()
print(time.ctime(), 'get %s from queue' % value)
if __name__ == '__main__':
# 主进程创建queue并传递给子进程
q = Queue()
pw = Process(target=write, args=(q,))
pr = Process(target=read, args=(q,))
pw.start()
pr.start()
pw.join()
pr.terminate()
使用maxsize,可以为队列设置最大长度,当为maxsize<=0时,队列的最大长度会被设置为一个非常大的值 .
put(self, obj, block=True, timeout=None)
1、block为True,若队列已满,并且timeout为正值,该方法会阻塞timeout指定的时间,直到队列中有出现剩余空间,如果超时,会抛出Queue.Full异常
2、block为False,若队列已满,立即抛出queue.Full异常
get(self, block=True, timeout=None)
block为True,若队列为空,并且timeout为正值,该方法会阻塞timeout指定的时间,直到队列中有出现新的数据,如果超时,会抛出Queue.Empty异常
block为False,若队列为空,立即抛出queue.Empty异常