python —— 进程
目录
1. 进程
1.进程就是一个运行中的程序(是对正在运行程序的一个抽象)。
-
2.程序和进程之间的区别:
- 程序只是一个文件
- 进程是这个文件被cpu运行起来了
- 程序是永久的,进程是暂时的。
-
3.进程—是计算机中最小的资源分配单位
在操作系统中的唯一标识符 :pid注意:同一个程序执行两次,就会在操作系统中出现两个进程,所以我们可以同时运行一个软件,分别做不同的事情也不会混乱。
4.进程是数据隔离的,进程的数据隔离是由操作系统完成的。
2. 进程调度
要想多个进程交替运行,操作系统必须对这些进程进行调度,这个调度也不是随即进行的,而是需要遵循一定的法则,由此就有了进程的调度算法。
进程的调度是由操作系统完成的(我们不能干预)。
操作系统调度进程的算法:
- 1.短作业优先算法
- 2.先来先服务算法
- 3.时间片轮转
- 4.多级反馈算法(融合了前三种算法)
3. 进程的并行与并发
-
并行:
两个程序、两个cpu,每个程序分别占用一个cpu自己执行自己的
看起来是同时执行,实际在每一个时间点上都在各自执行着
-
并发:
两个程序、一个cpu,每个程序交替的在一个cpu上执行
看起来在同时执行,但是实际上仍然是串行
4. 同步异步阻塞非阻塞
-
1.同步
同步就是一个任务的完成需要依赖另外一个任务时,只有等待被依赖的任务完成后,依赖的任务才能算完成,这是一种可靠的任务序列。要么成功都成功,失败都失败,两个任务的状态可以保持一致。
简单说:调用一个方法,要等待这个方法结束。
如:烧水 和 吹头发 正在烧水 停下烧水这个动作去吹头发 吹完头发之后继续烧水
-
2.异步
异步是不需要等待被依赖的任务完成,只是通知被依赖的任务要完成什么工作,依赖的任务也立即执行,只要自己完成了整个任务就算完成了
。至于被依赖的任务最终是否真正完成,依赖它的任务无法确定,所以它是不可靠的任务序列
。简单说:调用一个方法,不等待这个方法结束,也不关心这个方法做了什么。
如:烧水 和 吹头发 正在烧水 开始吹头发,但烧水也在继续进行
-
3.阻塞
cpu不工作
阻塞影响了程序运行的效率。
-
4.非阻塞
cpu工作
-
5.同步阻塞
效率最低
例1:conn.recv
socket 阻塞的tcp协议的时候
例2:你专心排队,什么别的事都不做。
-
6.同步非阻塞
实际上是效率低下的
例1:func() 没有io操作
socket 非阻塞的tcp协议的时候
调用函数(这个函数内部不存在io操作)
例2:你一边打着电话一边还需要抬头看到底队伍排到你了没有,如果把打电话和观察排队的位置看成是程序的两个操作的话,这个程序需要在这两种不同的行为之间来回的切换,效率可想而知是低下的。
-
7.异步非阻塞
效率更高
例:把func扔到其他任务里去执行了
我本身的任务和func任务各自执行各自的,没有io操作
-
8.异步阻塞
异步操作是可以被阻塞住的,只不过它不是在处理消息时阻塞,而是在等待消息通知时被阻塞。
如果在银行等待办理业务的人
采用的是异步的方式去等待消息被触发(通知)
,也就是领了一张小纸条,假如在这段时间里他不能离开银行做其它的事情,那么很显然,这个人被阻塞在了这个等待的操作上面;
5. 进程的三状态图
在了解其他概念之前,我们首先要了解进程的几个状态。在程序运行的过程中,由于被操作系统的调度算法控制,程序会进入几个状态:就绪,运行和阻塞。
-
1.就绪(ready)状态
当进程已分配到除cpu以外的所有必要的资源,只要获得处理机便可立即执行,这时的进程状态称为就绪状态。
2.执行/运行(running)状态当进程已获得处理机,其程序正在处理机上执行,此时的进程状态称为执行状态。
3.阻塞(blocked)状态正在执行的进程,由于等待某个事件发生而无法执行时,便放弃处理机而处于阻塞状态。引起进程阻塞的事件可有多种,例如,等待i/o完成、申请缓冲区不能满足、等待信件(信号)等。
6. multiprocessing模块
multiprocessing—— multi:multiple 多元的 processing 进程
import os import time print('start') time.sleep(20) print(os.getpid(),os.getppid(),'end')
os.getpid() 获取当前进程的pid
pid process id 子进程
ppid parent process id 父进程
父进程:在父进程中创建子进程
- 在pycharm中启动的所有py程序都是pycharm的子进程
import os import time from multiprocessing import process def func(): print('start',os.getpid()) time.sleep(1) print('end',os.getpid()) if __name__ == '__main__': p = process(target=func) p.start() # 异步 调用开启进程的方法,但是并不等待这个进程真的开启 print('main :',os.getpid()) import os import time from multiprocessing import process def eat(): print('start eating',os.getpid()) time.sleep(1) print('end eating',os.getpid()) def sleep(): print('start sleeping',os.getpid()) time.sleep(1) print('end sleeping',os.getpid()) if __name__ == '__main__': p1 = process(target=eat) # 创建一个即将要执行eat函数的进程对象 p1.start() # 开启第一个子进程 p2 = process(target=sleep) # 创建一个即将要执行sleep函数的进程对象 p2.start() # 开启第二个子进程 print('main :',os.getpid())
1.小知识补充:
if __name__ == '__main__'
:
- 控制当这个py文件被当作脚本直接执行的时候,就执行这里面的代码
- 当这个py文件被当作模块导入的时候,就不执行这里面的代码
__name__
有两种情况:
-
__name__ == '__main__'
执行的文件就是
__name__
所在的文件 -
`name == '文件名'``
`
__name__
所在的文件被导入执行的时候
2.操作系统创建进程的方式不同:
-
windows操作系统执行开启进程的代码
实际上新的子进程需要通过import父进程的代码来完成数据的导入工作(再一次执行父进程文件中的代码来获取父进程中的数据),所以有一些内容我们只希望在父进程中完成,就写在if
__name__ == '__main__'
:下面 -
ios linux操作系统创建进程 fork
正常的写就可以,不需要写if
__name__ == '__main__'
3.主进程和子进程之间的关系:
- 主进程没结束 :等待子进程结束
- 主进程负责回收子进程的资源
- 如果子进程执行结束,父进程没有回收资源,那么这个子进程会变成一个僵尸进程
主进程的结束逻辑:
- 主进程的代码结束,等待所有的子进程结束,给子进程回收资源,主进程结束。
主进程怎么知道子进程结束了的呢? —— 基于网络、文件
4.join方法
join方法:同步阻塞,直到子进程结束就结束
把一个进程的结束事件封装成一个join方法
执行join方法的效果:就是 阻塞,直到这个子进程执行结束就结束阻塞。
# 开起一个子进程 import time from multiprocessing import process def send_mail(): time.sleep(3) print('发送了一封邮件') if __name__ == '__main__': p = process(target=send_mail) p.start() # 异步 非阻塞 # time.sleep(5) print('join start') p.join() # 同步 阻塞,直到p对应的进程结束之后才结束阻塞 print('5000封邮件已发送完毕') # 开起多个子进程 # 开启10个进程,给公司的5000个人发邮件,发送完邮件之后,打印一个消息“5000封邮件已发送完毕” import time import random from multiprocessing import process def send_mail(a): time.sleep(random.random()) print('发送了一封邮件',a) if __name__ == '__main__': l = [] for i in range(10): p = process(target=send_mail,args=(i,)) p.start() l.append(p) print(l) for p in l:p.join() # 阻塞 直到上面的十个进程都结束 print('5000封邮件已发送完毕') # 所有的子进程都结束之后要执行的代码写在这里
7. 守护进程
1.有一个参数可以把一个子进程设置为一个守护进程
import time from multiprocessing import process def son1(a,b): while true: print('is alive') time.sleep(0.5) def son2(): for i in range(5): print('in son2') time.sleep(1) if __name__ == '__main__': p = process(target=son1,args=(1,2)) p.daemon = true p.start() # 把p子进程设置成了一个守护进程 p2 = process(target=son2) p2.start() time.sleep(2) # son1只执行了2s
守护进程是随着主进程的代码结束而结束的
应用场景:
- 生产者消费者模型的时候
- 和守护线程做对比的时候
所有的子进程都必须在主进程结束之前结束,由主进程来负责回收资源
p.is_alive() 判断一个子进程是否还活着
p.terminate() 强制结束一个子进程
import time from multiprocessing import process def son1(): while true: print('is alive') time.sleep(0.5) if __name__ == '__main__': p = process(target=son1) p.start() # 异步 非阻塞 print(p.is_alive()) time.sleep(1) p.terminate() # 异步的 非阻塞 print(p.is_alive()) # 进程还活着 因为操作系统还没来得及关闭进程 time.sleep(0.01) print(p.is_alive()) # 操作系统已经响应了我们要关闭进程的需求,再去检测的时候,得到的结果是进程已经结束了
什么是异步非阻塞?—— terminate
2.process类使用面向对象的方式:
def start(self) ——> none :……开起了一个子进程,调用run。
def run(self) ——> none :……
import os import time from multiprocessing import process class myprocecss1(process): def __init__(self,x,y): # 传参数,不要忘记写init方法 self.x = x self.y = y super().__init__() def run(self): # 子进程中要做的事情写在run方法中 print(self.x,self.y,os.getpid()) while true: print('is alive') time.sleep(0.5) class myprocecss2(process): def run(self): for i in range(5): print('in son2') time.sleep(1) if __name__ == '__main__': mp = myprocecss1(1,2) mp.daemon = true mp.start() print(mp.is_alive()) mp.terminate() mp2 = myprocecss2() mp2.start() print('main :',os.getpid()) time.sleep(1)
process类的总结:
1.开启进程的方式
-
1.面向函数:
def 函数名:要在子进程中执行的代码
p = process(target= 函数名,args=(参数1,)) -
2.面向对象:
class 类名(process):
def init(self,参数1,参数2): # 如果子进程不需要参数可以不写
self.a = 参数1
self.b = 参数2
super().__init__()
def run(self): # 要在子进程中执行的代码
p = 类名(参数1,参数2)
2.process提供的操作进程的方法
- p.start() 开启进程 异步非阻塞
- p.terminate() 结束进程 异步非阻塞
- p.join() 同步阻塞
- p.isalive() 获取当前进程的状态
- daemon = true 设置为守护进程,守护进程永远在主进程的代码结束之后自动结束
8. 锁
并发 能够做的事儿,如:
[toc]
- 1.实现能够响应多个client端的server
- 2.抢票系统
1.如果在一个并发的场景下,涉及到某部分内容,是需要修改一些所有进程共享的数据资源,需要加锁来维护数据的安全。
2.在数据安全的基础上,才考虑效率问题
3.同步存在的意义:数据的安全性
在主进程中实例化 lock = lock(),把这把锁传递给子进程,在子进程中,对需要加锁的代码 进行 with lock:
- with lock相当于lock.acquire()和lock.release()
import time import json from multiprocessing import process,lock def search_ticket(user): with open('ticket_count') as f: dic = json.load(f) print('%s查询结果 : %s张余票'%(user,dic['count'])) def buy_ticket(user,lock): # with lock: # 相当于lock.acquire() + lock.release() # lock.acquire() # 给这段代码加上一把锁 time.sleep(0.02) with open('ticket_count') as f: dic = json.load(f) if dic['count'] > 0: print('%s买到票了'%(user)) dic['count'] -= 1 else: print('%s没买到票' % (user)) time.sleep(0.02) with open('ticket_count','w') as f: json.dump(dic,f) # lock.release() # 给这段代码解锁 def task(user, lock): search_ticket(user) with lock: buy_ticket(user, lock) if __name__ == '__main__': lock = lock() for i in range(10): p = process(target=task,args=('user%s'%i,lock)) p.start()
在进程中需要加锁的场景:
- 共享的数据资源(文件、数据库)
- 对资源进行修改、删除操作
加锁之后能够保证数据的安全性 但是也降低了程序的执行效率
9. 进程之间的通信
进程之间的数据是隔离的
from multiprocessing import process n = 100 def func(): global n n -= 1 if __name__ == '__main__': p_l = [] for i in range(10): p = process(target=func) p.start() p_l.append(p) for p in p_l:p.join() print(n) # 主进程数据中的变量的值 不会随着 子进程数据中变量值的修改而改变
进程之间的通信 - ipc(inter process communication)
queue 队列
queue 天生就是数据安全的
-
queue 基于文件家族的socket执行的,基于 pickle操作的,基于 lock实现的。
from multiprocessing import queue,process # 先进先出 def func(exp,q): ret = eval(exp) q.put({ret,2,3}) q.put(ret*2) q.put(ret*4) if __name__ == '__main__': q = queue() process(target=func,args=('1+2+3',q)).start() print(q.get()) print(q.get()) print(q.get())
pipe 管道
-
pipe 管道(不安全的) —— 基于文件家族的socket 、pickle
from multiprocessing import pipe pip = pipe() pip.send() pip.recv()
队列 = 管道 + 锁
import queue from multiprocessing import queue q = queue(5) q.put(1) q.put(2) q.put(3) q.put(4) q.put(5) # 当队列为满的时候再向队列中放数据 队列会阻塞 print('5555555') try: q.put_nowait(6) # 当队列为满的时候再向队列中放数据,会报错并且会丢失数据(不安全,不建议使用) except queue.full: pass print('6666666') print(q.get()) print(q.get()) print(q.get()) # 在队列为空的时候会发生阻塞 print(q.get()) # 在队列为空的时候会发生阻塞 print(q.get()) # 在队列为空的时候会发生阻塞 try: print(q.get_nowait()) # 在队列为空的时候 直接报错 except queue.empty:pass
ipc机制:
- 内置模块(基于文件):queue队列、pipe管道
- 第三方工具(软件)提供给我们的ipc机制(基于网络):redis / memcache / kafka / rabbitmq 发挥的都是消息中间件的功能
第三方ipc的优点:
- 并发需求
- 高可用
- 断电保存数据
- 解耦
队列:进程之间数据安全
管道:进程之间数据不安全
10. 解耦
解耦 :修改 复用 可读性
程序的解耦:把写在一起的大的功能分开成多个小的功能处理(如:登陆 注册)
生产者消费者模型:
什么是生产者消费者模型?
把一个产生数据并且处理数据的过程解耦,让生产数据的过程和处理数据的过程达到一个工作效率上的平衡。
-
中间的容器,在多进程中我们使用队列或者可被join的队列,做到控制数据的量:
- 当数据过剩的时候,队列的大小会控制这生产者的行为
- 当数据严重不足的时候,队列会控制消费者的行为
- 并且我们还可以通过定期检查队列中元素的个数来调节生产者消费者的个数
-
比如说:一个爬虫或者一个web程序的server端
-
爬虫:
请求网页的平均时间是0.3s
处理网页代码的时候是0.003s
100倍,每启动100个线程生产数据
启动一个线程来完成处理数据
-
web程序的server端:
每秒钟有6w条请求
一个服务每s中只能处理2000条
先写一个web程序,只负责一件事情,就是接收请求,然后把请求放到队列中
再写很多个server端,从队列中获取请求,然后处理,然后返回结果
-
import time import random from multiprocessing import process,queue def producer(q,name,food): for i in range(10): time.sleep(random.random()) fd = '%s%s'%(food,i) q.put(fd) print('%s生产了一个%s'%(name,food)) def consumer(q,name): while true: food = q.get() if not food:break time.sleep(random.randint(1,3)) print('%s吃了%s'%(name,food)) def cp(c_count,p_count): q = queue(10) for i in range(c_count): process(target=consumer, args=(q, 'alex')).start() p_l = [] for i in range(p_count): p1 = process(target=producer, args=(q, 'wusir', '烧饼')) p1.start() p_l.append(p1) for p in p_l:p.join() for i in range(c_count): q.put(none) if __name__ == '__main__': cp(2,3)
# 用生产者消费者模式实现爬虫的例子 from multiprocessing import process,queue import requests import re import json def producer(q,url): response = requests.get(url) q.put(response.text) def consumer(q): while true: s = q.get() if not s:break com = re.compile( '<div class="item">.*?<div class="pic">.*?<em .*?>(?p<id>\d+).*?<span class="title">(?p<title>.*?)</span>' '.*?<span class="rating_num" .*?>(?p<rating_num>.*?)</span>.*?<span>(?p<comment_num>.*?)评价</span>', re.s) ret = com.finditer(s) for i in ret: print({ "id": i.group("id"), "title": i.group("title"), "rating_num": i.group("rating_num"), "comment_num": i.group("comment_num")} ) if __name__ == '__main__': count = 0 q = queue(3) p_l = [] for i in range(10): url = 'https://movie.douban.com/top250?start=%s&filter='%count count+=25 p = process(target=producer,args=(q,url,)).start() p_l.append(p) p = process(target=consumer, args=(q,)).start() for p in p_l:p.join() q.put(none)
11. joinablequeue类
joinablequeue 与 queue使用成本一样,但joinablequeue 更严紧一些
import time import random from multiprocessing import joinablequeue,process def producer(q,name,food): for i in range(10): time.sleep(random.random()) fd = '%s%s'%(food,i) q.put(fd) print('%s生产了一个%s'%(name,food)) q.join() def consumer(q,name): while true: food = q.get() time.sleep(random.random()) print('%s吃了%s'%(name,food)) q.task_done() if __name__ == '__main__': jq = joinablequeue() p =process(target=producer,args=(jq,'wusir','烧饼')) p.start() c = process(target=consumer,args=(jq,'alex')) c.daemon = true c.start() p.join()
12. 进程之间的数据共享-manager类
multiprocessing中有一个manager类,封装了所有和进程相关的(包括:数据共享、数据传递)、与共享相关的数据类型,但是对于 字典 、列表这一类的数据操作的时候会产生数据不安全,需要加锁解决问题,并且需要尽量少的使用这种方式。
```python
from multiprocessing import manager,process,lock
def func(dic,lock):
with lock:
dic['count'] -= 1
if name == 'main':
# m = manager()
with manager() as m:
l = lock()
dic = m.dict({'count':100})
p_l = []
for i in range(100):
p = process(target=func,args=(dic,l))
p.start()
p_l.append(p)
for p in p_l:p.join()
print(dic)
``
上一篇: 分析Python读取文件时的路径问题