进程
操作系统背景知识
发展历史:
1.第一代计算机(1940~1955):真空管和穿孔卡片
特点:没有操作系统的概念,所有的程序设计都是直接操控硬件。
优点:每个人独享,可以自己调试代码,找到bug。
缺点:浪费计算机资源,人机矛盾。
2.第二代就算机(1955~1965):晶体管和批处理系统
特点:把代码都赞到一块,让一个CPU共享,但是还是一个一个的去运行,还是顺序算法(串行)
优点:批处理,节省了机时。
缺点:有人的参与了,搬过来搬过去的麻烦,拖慢程序运行的速度
3.第三代计算机(1965~1980):集成电路芯片和多道程序设计
第三代计算机的操作系统广泛应用了第二代计算机的操作系统没有的关键技术:多道技术
多道技术:(指的是多道/个程序)
1.空间上的复用:内存要支持同时跑进多个程序
2.时间上的复用:多个程序要让它们能切换(什么时候要切?一个程序占用的时间过长要切;当CPU遇到IO阻塞时,等待的时间要切
多道操作系统:
3.批处理系统的一个重要缺点:不提供人机交互能力,给用户使用计算机带来不便。
4.批处理系统的追求目标:提高系统资源利用率和系统吞吐量,以及作业流程的自动化。
分时操作系统:
切换要占时间 :单纯的分时系统没有提高CPU的利用率
提高了用户体验
并没有提高cpu的效率,反而使得计算机的效率降低了
4.第四代计算机(1980~至今):个人计算机
实时系统
分时——现在流行的PC,服务器都是采用这种运行模式,即把CPU的运行分成若干时间片分别处理不同的运算请求 linux系统
实时——一般用于单片机上、PLC等,比如电梯的上下控制中,对于按键等动作要求进行实时处理
通用操作系统
通用操作系统:具有多种类型操作特征的操作系统。可以同时兼有多道批处理、分时、实时处理的功能,或其中两种以上的功能。
操作系统的进一步发展
网络操作系统
分布式操作系统
操作系统的作用
1.为应用程序提供如何使用硬件资源的抽象
2.把多个程序对硬件的竞争变得有序化(管理应用程序)
什么是进程
进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。在早期面向进程设计的计算机结构中,进程是程序的基本执行实体;在当代面向线程设计的计算机结构中,进程是线程的容器。程序是指令、数据及其组织形式的描述,进程是程序的实体。
进程调度
1.先来先服务调度算法
2.短作业优先调度算法
3.时间片轮转法
4.多级反馈队列
进程的并行与并发
并行 : 并行是指两者同时执行,比如赛跑,两个人都在不停的往前跑;(资源够用,比如三个线程,四核的CPU )
并发 : 并发是指资源有限的情况下,两者交替轮流使用资源,比如一段路(单核CPU资源)同时只能过一个人,A走一段后,让给B,B用完继续给A ,交替使用,目的是提高效率。
区别:
并行是从微观上,也就是在一个精确的时间片刻,有不同的程序在执行,这就要求必须有多个处理器。
并发是从宏观上,在一个时间段上可以看出是同时执行的,比如一个服务器同时处理多个session。
并发是包含并行的
同步异步阻塞非阻塞
1)就绪(Ready)状态
当进程已分配到除CPU以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
2)执行/运行(Running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。
3)阻塞(Blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待I/O完成、申请缓冲区不能满足、等待信件(信号)等。
同步和异步
所谓同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列
。要么成功都成功,失败都失败,两个任务的状态可以保持一致。
所谓异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了
。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列
。
阻塞与非阻塞
阻塞和非阻塞这两个概念与程序(线程)等待消息通知(无所谓同步或者异步)时的状态有关。也就是说阻塞与非阻塞主要是程序(线程)等待消息通知时的状态角度来说的
同步/异步与阻塞/非阻塞
- 同步阻塞形式
效率最低。拿上面的例子来说,就是你专心排队,什么别的事都不做。
- 异步阻塞形式
如果在银行等待办理业务的人采用的是异步的方式去等待消息被触发(通知)
,也就是领了一张小纸条,假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面;
异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。
- 同步非阻塞形式
实际上是效率低下的。
想象一下你一边打着电话一边还需要抬头看到底队伍排到你了没有,如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换
,效率可想而知是低下的。
- 异步非阻塞形式
效率更高,
因为打电话是你(等待者)的事情,而通知你则是柜台(消息触发机制)的事情,程序没有在两种不同的操作中来回切换
。
比如说,这个人突然发觉自己烟瘾犯了,需要出去抽根烟,于是他告诉大堂经理说,排到我这个号码的时候麻烦到外面通知我一下,那么他就没有被阻塞在这个等待的操作上面,自然这个就是异步+非阻塞的方式了。
很多人会把同步和阻塞混淆,是因为很多时候同步操作会以阻塞的形式表现出来
,同样的,很多人也会把异步和非阻塞混淆,因为异步操作一般都不会在真正的IO操作处被阻塞
。
进程的创建与结束
通用系统(跑很多应用程序),需要有系统运行过程中创建或撤销进程的能力,主要分为4中形式创建新的进程:
1. 系统初始化(查看进程linux中用ps命令,windows中用任务管理器,前台进程负责与用户交互,后台运行的进程与用户无关,运行在后台并且只在需要时才唤醒的进程,称为守护进程,如电子邮件、web页面、新闻、打印)
2. 一个进程在运行过程中开启了子进程(如nginx开启多进程,os.fork,subprocess.Popen等)
3. 用户的交互式请求,而创建一个新进程(如用户双击暴风影音)
4. 一个批处理作业的初始化(只在大型机的批处理系统中应用)
无论哪一种,新进程的创建都是由一个已经存在的进程执行了一个用于创建进程的系统调用而创建的。
进程的结束
1. 正常退出(自愿,如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)
2. 出错退出(自愿,python a.py中a.py不存在)
3. 严重错误(非自愿,执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try...except...)
4. 被其他进程杀死(非自愿,如kill -9)
multiprocess模块
import time
from multiprocessing import Process
def f(name):
print('hello', name)
print('我是子进程')
if __name__ == '__main__':
p = Process(target=f, args=('bob',))
p.start()
time.sleep(1)
print('执行主进程的内容了')
在python中启动的第一个子进程
能给子进程中传参数
def func(arg):
for i in range(10):
time.sleep(0.5)
print('子进程%s :'%arg,os.getpid(),os.getppid())
if __name__ == '__main__':
print('主进程',os.getpid(),os.getppid())
p = Process(target=func,args=(1,))
p.start()
for i in range(10):
time.sleep(0.3)
print('*'*i)
主进程和子进程之间数据隔离
count = 100
def func():
global count
count -= 1
print('子进程 :',count)
if __name__ == '__main__':
print('主进程',os.getpid(),os.getppid())
p = Process(target=func)
p.start()
time.sleep(2)
print('主进程 :',count)
启动多个进程
def func(arg):
print('子进程%s :'%arg ,os.getpid(),os.getppid())
if __name__ == '__main__':
for i in range(10):
Process(target=func,args=(i,)).start()
子进程和父进程之间的关系
def func(arg):
print('子进程%s :'%arg ,os.getpid(),os.getppid())
time.sleep(5)
print('子进程end')
if __name__ == '__main__':
for i in range(10):
Process(target=func,args=(i,)).start()
print('父进程*******')
1.父进程和子进程的启动时异步的
父进程只负责通知操作系统启动子进程
接下来的工作由操作系统接手 父进程继续执行
2.父进程执行完毕之后并不会直接结束程序,
而是会等待所有的子进程都执行完毕之后才结束
父进程要负责回收子进程的资源
# 多个进程同时运行,join方法
def func(index):
time.sleep(random.random())
print('第%s个邮件已经发送完毕'%index)
if __name__ == '__main__':
p_lst = []
for i in range(10):
p = Process(target=func,args=(i,))
p.start()
p_lst.append(p)
for p in p_lst:
p.join()
print('10个邮件已经发送完毕')
通过继承Process类开启进程
class MyProcess(Process):
def run(self):
print('子进程:',os.getpid(),os.getppid())
if __name__ == '__main__':
p = MyProcess()
p.start() # 开启一个子进程,让这个子进程执行run方法
print('主进程:',os.getpid())
# 通过继承Process类开启进程,给子进程传参数
class MyProcess(Process):
def __init__(self,arg):
super().__init__()
self.arg = arg
def run(self):
time.sleep(1)
print('子进程:',os.getpid(),os.getppid(),self.arg)
if __name__ == '__main__':
# p = MyProcess('参数')
# p.start() # 开启一个子进程,让这个子进程执行run方法
# p.join()
# print('主进程:',os.getpid())
for i in range(10):
p = MyProcess('参数%s'%i)
p.start() # 开启一个子进程,让这个子进程执行run方法
print('主进程:', os.getpid())
守护进程
import time
from multiprocessing import Process
def func():
print('子进程 start')
time.sleep(5)
print('子进程 end')
if __name__ == '__main__':
p = Process(target=func)
p.daemon = True # 设置p为一个守护进程,必须在start之前完成,要不然不会执行
p.start()
time.sleep(0.1)
print('主进程')
守护进程会随着主进程的代码执行完毕而结束
如果主进程代码已经执行完毕,但是子进程还没执行完,守护进程都不会继续执行
守护进程会随着主进程的代码执行完毕而结束
主进程会等待子进程结束,守护进程只等待主进程代码结束就结束了
锁
import time
import json
from multiprocessing import Process,Lock
def search(person):
with open('ticket') as f:
dic = json.load(f)
time.sleep(0.2)
print('%s查询余票 :'%person,dic['count'])
def get_ticket(person):
with open('ticket') as f:
dic = json.load(f)
time.sleep(0.2)
if dic['count'] > 0:
print('%s买到票了'%person)
dic['count'] -= 1
time.sleep(0.2)
with open('ticket','w') as f:
json.dump(dic,f)
else:
print('%s没买到票'%person)
def ticket(person,lock):
search(person)
lock.acquire()
get_ticket(person)
lock.release()
if __name__ == '__main__':
lock = Lock()
for i in range(10):
p = Process(target=ticket,args=('person%s'%i,lock))
p.start()
p.join()
为了保证数据的安全
在异步的情况下,多个进程又可能同时修改同一份资源
就给这个修改的过程加上锁
加锁
降低了程序的效率,让原来能够同时执行的代码变成顺序执行了,异步变同步的过程
保证了数据的安全
#互斥锁Lock
lock = Lock()
lock.acquire()
print('456')
# lock.release()
lock.acquire()
print('123')
信号量的实现机制
计数器 + 锁实现的
# 4个屋子
# 10个人
import time
import random
from multiprocessing import Process,Semaphore
def ktv(person,sem):
sem.acquire()
print('%s走进ktv'%person)
time.sleep(random.randint(1,5))
print('%s走出ktv'%person)
sem.release()
if __name__ == '__main__':
sem = Semaphore(4)
for i in range(10):
p = Process(target=ktv,args=('person%s'%i,sem))
p.start()
事件
Event
阻塞事件 :wait()方法
wait是否阻塞是看event对象内部的一个属性
控制这个属性的值
set() 将这个属性的值改成True
clear() 将这个属性的值改成False
is_set() 判断当前的属性是否为True
import time
import random
from multiprocessing import Process,Event
def traffic_light(e):
print('\033[31m红灯亮\033[0m')
while True:
if e.is_set():
time.sleep(2)
print('\033[31m红灯亮\033[0m')
e.clear()
else:
time.sleep(2)
print('\033[32m绿灯亮\033[0m')
e.set()
def car(e,i):
if not e.is_set():
print('car %s 在等待' % i)
e.wait()
print('car %s 通过了'%i)
if __name__ == '__main__':
e = Event()
p = Process(target=traffic_light,args=(e,))
p.daemon = True
p.start()
p_lst = []
for i in range(20):
time.sleep(random.randrange(0,3,2))
p = Process(target=car,args=(e,i))
p.start() # car19
p_lst.append(p)
for p in p_lst:p.join()
进程间通信——队列和管道
from multiprocessing import Queue
import queue
# 先进先出
q = Queue(2)
q.put(1)
q.put(2)#常用
print(q.get())#常用,会阻塞
print(q.get())
try:
print(q.get_nowait())#非阻塞,常用
except queue.Empty:
print('empty')
print(q.empty())#再多进程下不准
print(q.full())#再多进程下不准
put_nowait()##非阻塞,不常用
print(q.empty())#再多进程下不准
print(q.full())#再多进程下不准
from multiprocessing import Process,Queue
def consume(q):
print('son-->',q.get())
q.put('abc')
if __name__ == '__main__':
q = Queue()
p = Process(target=consume,args=(q,))
p.start()
q.put({'123':123})
p.join()
print('Foo-->',q.get())
打印结果:
son--> {'123': 123}
Foo--> abc
生产者消费者模型
import time import random from multiprocessing import Process,Queue def consumer(q,name): # 处理数据 while True: food = q.get() if food is None:break time.sleep(random.uniform(0.5,1)) print('%s吃了一个%s' % (name, food)) def producer(q,name,food): # 获取数据 for i in range(10): time.sleep(random.uniform(0.3,0.8)) print('%s生产了%s%s'%(name,food,i)) q.put(food+str(i)) if __name__ == '__main__': q = Queue() c1 = Process(target=consumer,args=(q,'alex')) c2 = Process(target=consumer,args=(q,'wusir')) c1.start() c2.start() p1 = Process(target=producer, args=(q, '杨宗河','泔水')) p2 = Process(target=producer, args=(q, '何思浩','鱼刺和骨头')) p1.start() p2.start() p1.join() p2.join() q.put(None) # 有几个consumer就需要放几个None q.put(None)
JoinableQueue
from multiprocessing import JoinableQueue # JoinableQueue 类 # put # get # task_done 通知队列已经有一个数据被处理了 # q.join() # 阻塞直到放入队列中所有的数据都被处理掉(有多少个数据就接收到了多少taskdone) import time import random from multiprocessing import Process,JoinableQueue def consumer(q,name): # 处理数据 while True: food = q.get() time.sleep(random.uniform(0.5,1)) print('%s吃了一个%s' % (name, food)) q.task_done() def producer(q,name,food): # 获取数据 for i in range(10): time.sleep(random.uniform(0.3,0.8)) print('%s生产了%s%s'%(name,food,i)) q.put(food+str(i)) if __name__ == '__main__': jq = JoinableQueue() c1 = Process(target=consumer,args=(jq,'alex')) c2 = Process(target=consumer,args=(jq,'wusir')) c1.daemon = True c2.daemon = True c1.start() c2.start() p1 = Process(target=producer,args=(jq,'杨宗河','泔水')) p2 = Process(target=producer,args=(jq,'何思浩','鱼刺和骨头')) p1.start() p2.start() p1.join() # 生产者要先把所有的数据都放到队列中 p2.join() jq.join()
管道(了解)
队列是基于管道实现的
管道是基于socket实现的
队列 + 锁 简便的IPC机制 使得 进程之间数据安全
管道 进城之间数据不安全 且存取数据复杂
socket + pickle
import time from multiprocessing import Pipe,Process def consumer(left,right): time.sleep(1) print(right.recv()) if __name__ == '__main__': left,right = Pipe() Process(target=consumer,args=(left,right)).start() left.send(1234) def consumer(left,right): left.close() while True: try: print(right.recv()) except EOFError: break if __name__ == '__main__': left,right = Pipe() Process(target=consumer,args=(left,right)).start() right.close() for i in range(10): left.send('泔水%s'%i) left.close()
pipe的端口管理不会随着某一个进程的关闭就关闭
操作系统来管理进程对这些端口的使用
left,right
left,right
操作系统管理4个端口 每关闭一个端口计数-1,直到所有的端口都关闭了,
剩余1个端口的时候 recv就会报错
进程池
提交任务的方法 同步提交,去返回值,慢
import os
import time
from multiprocessing import Pool
def task(num):
time.sleep(1)
print('%s : %s'%(num,os.getpid()))
return num**2
if __name__ == '__main__':
p = Pool()
for i in range(20):
res = p.apply(task,args=(i,)) # 提交任务的方法 同步提交,慢
print('-->',res)
同步提交 apply
返回值 : 子进程对应函数的返回值
一个一个顺序执行的,并没有任何并发效果
提交任务的方法 异步提交,快
import os
import time
from multiprocessing import Pool
def task(num):
time.sleep(1)
print('%s : %s'%(num,os.getpid()))
return num**2
if __name__ == '__main__':
p = Pool()
for i in range(20):
p.apply_async(task,args=(i,)) # 提交任务的方法 异步提交,快
p.close()
p.join()
提交任务的方法 取返回值,异步提交
def task(num):
time.sleep(1)
print('%s : %s'%(num,os.getpid()))
return num**2
#
if __name__ == '__main__':
p = Pool()
res_lst = []
for i in range(20):
res = p.apply_async(task,args=(i,)) # 提交任务的方法 取返回值,异步提交
res_lst.append(res)
for res in res_lst:
print(res.get())
异步提交 apply_async
没有返回值,要想所有任务能够顺利的执行完毕
p.close()
p.join() # 必须先close再join,阻塞直到进程池中的所有任务都执行完毕
有返回值的情况下
res.get() # get不能再提交任务之后立刻执行,应该是先提交所有的任务再通过get获取结果
map()方法
import time
from multiprocessing import Pool,Queue,Pipe
def func(q):
print(q)
time.sleep(1)
return q
if __name__ == '__main__':
p = Pool()
ret = p.map(func,range(20))
for i in ret:
print('--> ',i)
异步提交的简化版本
自带close和join方法
有返回值
回调函数
import os
from multiprocessing import Pool
def func(i):
print('第一个任务',os.getpid())
return '*'*i,123
def call_back(res):
print('回调函数 :',os.getpid())
print('res -->',res)
if __name__ == '__main__':
p = Pool()
print('主进程 : ',os.getpid())
p.apply_async(func,args=(1,),callback=call_back)
p.close()
p.join()
当func执行完毕之后执行callback函数
func的返回值会作为callback的参数
回调函数是通过主进程实现的
url_lst = [ 'http://www.baidu.com', 'http://www.sohu.com', 'http://www.sogou.com', 'http://www.4399.com', 'http://www.cnblogs.com', ] import re from urllib.request import urlopen from multiprocessing import Pool def get_url(url): response = urlopen(url) ret = re.search('www\.(.*?)\.com',url) print('%s finished'%ret.group(1)) return ret.group(1),response.read(),1 def call(a): url,con = a with open(url+'.html','wb') as f: f.write(con) if __name__ == '__main__': p = Pool() for url in url_lst: p.apply_async(get_url,args=(url,),callback=call) p.close() p.join()
子进程有大量的计算要去做,回调函数等待结果做简单处理
子进程去访问网页,主进程处理网页的结果