sailan
一、什么是进程
进程就是正在执行的程序,执行程序的过程可以说是一个任务,负责执行任务的则是CPU。
二、进程和程序的区别
程序没执行仅仅只是一堆代码,进程则指的是程序的运行过程。
三、并发与并行及串行
并发: 是伪并行,即看起来是同时运行。单个cpu+多道技术就可以实现并发,(并行也属于并发)
并行: 多个任务是真的在同时运行,只有多个cpu才有并行的概念
单核下,可以利用多道技术,多个核,每个核也都可以利用多道技术(多道技术是针对单核而言的) 有四个核,六个任务,这样同一时间有四个任务被执行,假设分别被分配给了cpu1,cpu2,cpu3,cpu4, 一旦任务1遇到I/O就被迫中断执行,此时任务5就拿到cpu1的时间片去执行,这就是单核下的多道技术 而一旦任务1的I/O结束了,操作系统会重新调用它(需知进程的调度、分配给哪个cpu运行,由操作系统说了算),可能被分配给四个cpu中的任意一个去执行;
串行: 多个任务依次运行,一个运行完毕再运行下一个;
四、进程的创建
在UNIX中该系统调用是fork:
fork会创建一个与父进程一模一样的副本,二者有相同的存储映像、同样的环境字符串和同样的打开文件(在shell解释器进程中,执行一个命令就会创建一个子进程)
在windows中该系统调用是CreateProcess:
CreateProcess既处理进程的创建,也负责把正确的程序装入新进程。
关于创建的子进程,UNIX和windows;
相同的是:
进程创建后,父进程和子进程有各自不同的地址空间(多道技术要求物理层面实现进程之间内存的隔离),任何一个进程的在其地址空间中的修改都不会影响到另外一个进程。
不同的是:
在UNIX中,子进程的初始地址空间是父进程的一个副本,提示:子进程和父进程是可以有只读的共享内存区的。但是对于windows系统来说,从一开始父进程与子进程的地址空间就是不同的。
五、进程的终止
-
正常退出(如用户点击交互式页面的叉号,或程序执行完毕调用发起系统调用正常退出,在linux中用exit,在windows中用ExitProcess)
-
出错退出(python a.py中a.py不存在)
-
严重错误(执行非法指令,如引用不存在的内存,1/0等,可以捕捉异常,try…except…)
-
被其他进程杀死(如kill -9)
六、进程的状态
程序运行的三种状态:运行态,就绪态,阻塞态;
两种情况下会导致一个进程在逻辑上不能运行,
- 进程挂起是自身原因,遇到I/O阻塞,便要让出CPU让其他进程去执行,这样保证CPU一直在工作;
- 与进程无关,是操作系统层面,可能会因为一个进程占用时间过多,或者优先级等原因,而调用其他的进程去使用CPU。
优化程序效率的核心法则: 降低IO操作(硬盘IO、网络IO)
七、程序调用的方式
同步调用:
所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不会返回。按照这个定义,其实绝大多数函数都是同步调用。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。
异步调用:
异步的概念和同步相对。当一个异步功能调用发出后,调用者不能立刻得到结果。当该异步功能完成后,通过状态、通知或回调来通知调用者。如果异步功能用状态来通知,那么调用者就需要每隔一定时间检查一次,效率就很低(有些初学多线程编程的人,总喜欢用一个循环去检查某个变量的值,这其实是一 种很严重的错误)。如果是使用通知的方式,效率则很高,因为异步功能几乎不需要做额外的操作。至于回调函数,其实和通知没太多区别。
阻塞:
阻塞调用是指调用结果返回之前,当前线程会被挂起(如遇到io操作)。函数只有在得到结果之后才会将阻塞的线程激活。有人也许会把阻塞调用和同步调用等同起来,实际上他是不同的。对于同步调用来说,很多时候当前线程还是激活的,只是从逻辑上当前函数没有返回而已。
非阻塞:
非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前也会立刻返回,同时该函数不会阻塞当前线程。
八、创建进程
1. multiprocessing模块
Python提供了multiprocessing。 multiprocessing模块用来开启子进程,并在子进程中执行我们定制的任务(比如函数),
multiprocessing模块的功能众多:支持子进程、通信和共享数据、执行不同形式的同步,提供了Process、Queue、Pipe、Lock等组件。
2. process类
在windows中Process()必须放到 # if __name__ == '__main__':下
由于Windows没有fork,多处理模块启动一个新的Python进程并导入调用模块。
如果在导入时调用Process(),那么这将启动无限继承的新进程(或直到机器耗尽资源)。
这是隐藏对Process()内部调用的原,使用if __name__ == “__main __”,这个if语句中的语句将不会在导入时被调用。
multiprocessing模块中用来创建进程的类;
Process([group [, target [, name [, args [, kwargs]]]]]),由该类实例化得到的对象,表示一个子进程中的任务(尚未启动)
强调:
1 需要使用关键字的方式来指定参数
2 args指定的为传给target函数的位置参数,是一个元组形式,必须有逗号
参数介绍:
1 group参数未使用,值始终为None
2 target表示调用对象,即子进程要执行的任务
3 args表示调用对象的位置参数元组
4 kwargs表示调用对象的字典
5 name为子进程的名称
方法介绍:
- p.run(): 进程启动时运行的方法,正是它去调用target指定的函数,我们自定义类的类中一定要实现该方法
- p.start(): 启动进程,并调用该子进程中的p.run()
- p.terminate(): 强制终止进程p,不会进行任何清理操作,如果p创建了子进程,该子进程就成了僵尸进程,使用该方法需要特别小心这种情况。如果p还保存了一个锁那么也将不会被释放,进而导致死锁
- p.is_alive(): 如果p仍然运行,返回True
- p.join([timeout]): 主线程等待p终止(强调:是主线程处于等的状态,而p是处于运行的状态)。timeout是可选的超时时间,需要强调的是,p.join只能join住start开启的进程,而不能join住run开启的进程
属性介绍:
1 p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程,当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程,必须在p.start()之前设置
2 p.name:进程的名称
3 p.pid:进程的pid
4 p.exitcode:进程在运行时为None、如果为–N,表示被信号N结束(了解即可)
其他方法:
windows:tasklist |findstr 进程id号
mac,Linux:ps aux | grep 进程id号
进程对象:t=Process(target=task, )或者是在进程内current_process()
t.pid或者current_process().pid 获取进程id号
导入os模块:
os.getpid() 获取进程id号
os.getppid() 获取父进程id号,子进程中获取父进程id,等于父进程的id号
3. 创建进程的两种方式
方式一:
from multiprocessing import Process
import time
def task(n):
print('我是子进程')
time.sleep(n)
print('子进程结束')
if __name__ == '__main__':
# args=(), kwargs={}
# t=Process(task,args=(1,))
t = Process(target=task, kwargs={'n': 1})
t.start() # 通知操作系统,开启进程,执行task函数
print('主')
方式二:
from multiprocessing import Process
import time
class Task(Process):
def __init__(self, n):
super().__init__()
self.n = n
def run(self):
print('我是子进程')
time.sleep(self.n)
print('子进程结束')
if __name__ == '__main__':
t = Task(1)
# t.run(1) # 不是调用t.run(),而是调用t.start()
t.start()
print('主')
4. join方法
join的使用:等待子进程执行完成,才会继续运行主进程的代码;
from multiprocessing import Process
import time
def task(n):
print('我是子进程')
time.sleep(n)
print('子进程结束')
if __name__ == '__main__':
ctime = time.time()
t = Process(target=task, kwargs={'n': 1})
t2 = Process(target=task, kwargs={'n': 2})
t.start()
t2.start()
t.join() # 等待t子进程执行完成
t2.join() # 等待t2子进程执行完成
print('主')
ctime2 = time.time()
print(ctime2 - ctime)
5. 进程间的数据相互隔离
from multiprocessing import Process
import time
age = 18
def task(n):
global age # 局部修改全局
age = 99
print('我是子进程')
time.sleep(n)
print('子进程结束')
print(age)
if __name__ == '__main__':
t = Process(target=task, kwargs={'n': 1})
t.start()
t.join() # 等待t子进程执行完成
print('主')
print(age) # 数据没有变,主进程中打印age和子进程的age没有半毛钱关系,数据是隔离的
九、进程调度
- 先来先服务
- 短作业优先
- 时间片轮转
- 多级反馈队列
十、僵尸进程和孤儿进程
僵尸进程:
进程结束了,资源还没来得及回收
孤儿进程:
主进程挂了,子进程还没结束,它就会被专门的进程接管
十一、守护进程
主进程创建守护进程;
守护进程会在主进程代码执行结束后就终止
守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
进程之间是互相独立的,主进程代码运行结束,守护进程随即终止
from multiprocessing import Process,current_process
import time
import os
def task():
print(os.getpid())
print('子进程')
time.sleep(200)
print('子进程结束')
if __name__ == '__main__':
t = Process(target=task, )
# 守护进程:主进程一旦结束,子进程也结束
t.daemon=True # 一定要加在启动之前
t.start()
time.sleep(1)
print('主进程结束')
十二、互斥锁
进程之间数据不共享,但是共享同一套文件系统,所以访问同一个文件,或同一个打印终端,是没有问题的,而共享带来的是竞争,竞争带来的结果就是错乱,如何控制,就是加锁处理
# 同时只有一个人能拿到,必须释放,其他人才能再次获取到
from multiprocessing import Process, Lock
import json
import time
import random
def search():
# 查票的函数
# 打开文件,读出ticket_count
with open('ticket', 'r', encoding='utf-8') as f:
dic = json.load(f)
print('余票还有:', dic.get('ticket_count'))
def buy():
with open('ticket', 'r', encoding='utf-8') as f:
dic = json.load(f)
time.sleep(random.randint(1, 3)) # 模拟一下网络延迟
if dic.get('ticket_count') > 0:
# 能够买票
dic['ticket_count'] -= 1
# 保存到文件中去
with open('ticket', 'w', encoding='utf-8') as f:
json.dump(dic, f)
print('买票成功')
else:
# 买票失败
print('买票失败')
# 写一个函数,先查票,再买票
def task(mutex):
search()
# 买票过程要加锁
# 买前加锁
# mutex.acquire()
# buy() # 10个进程变成了串行执行
# # 买后释放锁
# mutex.release()
with mutex: # 自动管理上下
buy()
if __name__ == '__main__':
# 锁的创建,在哪?主进程创建锁
mutex = Lock() # 创建一把锁
# 模拟十个人买票(开10个进程)
for i in range(10):
t = Process(target=task, args=(mutex,))
t.start()
十三、队列介绍
from multiprocessing import Queue
# 实例化得到要给对象
q=Queue(5) # 默认很大,可以放很多,写了个5,只能放5个
# 往管道中放值
q.put(1)
q.put_nowait(100) # 能放就放 不能放就抛出异常
# 从管道中取值
print(q.get())
print(q.get(timeout=0.1)) # 等0.1s还没有值,就结束
print(q.get_nowait()) # 不等了,有就是有,没有就没有 抛出异常
print(q.empty()) # 看一下队列是不是空的
print(q.full()) # 看一下队列是不是满的 返回布尔值
# 总结:
'''
q=Queue(队列大小)
# 放值
q.put(asdf)
q.put_nowait(asdf) # 队列满了,放不进去就不放了,报错
# 取值
q.get() # 从队列头部取出一个值
q.get_nowait() # 从队列头部取值,没有就抛错
# 队列是否为空,是否满
print(q.empty()) # 看一下队列是不是空的
print(q.full()) # 看一下队列是不是满的
'''
十四、IPC机制(Inter-Process Communication,进程间通信)
from multiprocessing import Process, current_process, Queue
import time
import os
def task1(q):
print('我是task1进程,我的id号是:%s'%os.getpid())
q.put('lqz is handsome')
def task2(q):
# res=q.get()
# print('我是task2进程,我的id号是:%s'%os.getpid(),res)
print('我是task2进程,我的id号是:%s'%os.getpid())
if __name__ == '__main__':
q = Queue(5)
t1 = Process(target=task1, args=(q,))
t1.start()
t2 = Process(target=task2, args=(q,))
t2.start()
print(q.get())