目录
并发编程
并发和串行
- 程序默认的执行方式是串行,即程序自上而下一行一行执行,必须把当前任务执行完毕才能执行下一个任务
学习并发的目的
就是编写可以同时执行多个任务的程序,从而提高效率
串行和并发都是程序处理任务的方式
## 实现并发的方式
- 多进程
- 多线程
- 协程
进程是什么
进程是指正在运行的程序,是操作系统调度以及进行资源分配的基本单位
当程序从硬盘读取到内存中运行的时候进程就产生了
多进程:指的是同一时间有多个程序被装入内存并执行
多进程的实现原理其实就是操作系统调度进程的原理
操作系统是什么
操作系统就是一款特殊的软件
操作系统和普通软件的区别:
- 操作系统可以直接与硬件交互
- 操作系统是受保护的,不能直接被修改
- 操作系统更加长寿,一旦完成基本不会修改,如系统内核
操作系统的主要功能
- 隐藏了硬件复杂的操作,提供了简单的api接口
- 将硬件的资源竞争变得有序可控
GUI图形化用户界面
操作系统发展史
掌握多道技术的实现原理,就是多进程的实现原理
第二代计算使用的是批处理系统,存在以下三个问题
- 需要人为参与
- 任务串行执行
- 程序员调试效率低
第三代计算机
使用SPOOLING联机计数
- 多道技术
多终端多用户
第四代计算机
大规模集成电路+多用户多终端
特点:具备GUI图形化界面,普通人也可以使用
多道技术
实现原理:
空间复用
同一时间加载多个任务到内存中,多个进程之间的内存区域需要相互隔离,这种隔离是物理隔离,为了保证数据安全性
时间复用
操作系统会在多个进程之间做切换
切换进程的两种情况:
1.但给一个进程遇到IO操作时会自动切换
2.当任务执行时间过长(超时)强制切换
切换进程前需要记录当前进程的状态,同时频繁的切换也消耗系统资源
当所有任务没有IO操作时,切换执行反而降低了效率,但是为了保证并发执行必须要牺牲效率
多道技术总结:切换+保存
有了多道技术,计算机就可以同时并发处理多个任务
并发编程中的重要概念
串行:从上到下依次执行
并发:多个任务同时执行,本质是多个进行不断在切换,由于切换速度快,所以感觉是同时运行
并行:真正意义上的同时运行,有几个核心就能并行几个任务,如果超过核心数就使用并发执行
以上三个都是用于描述处理任务的方式
一个进程的三种状态
阻塞:当程序遇到IO操作,无法继续执行代码时的一种状态
非阻塞 :程序没有遇到IO操作的状态,正常运行
就绪:程序等待CPU运行
阻塞非阻塞也可以用来描述执行任务的方式
三种状态的互相切换
程序正常运行,当运行时间超过阈值,会进入就绪状态,等待cpu执行完其他的进程后,恢复到运行状态继续执行
当程序遇到IO操作,会进入阻塞状态,当结束阻塞状态后,程序会进入就绪状态,等待cpu空闲后继续执行本程序
野指针和僵尸指针
野指针:内存中的地址被删除
僵尸指针:内存中的地址因为某种原因没有被清除
进程的创建和销毁
创建:
- 用户交互式请求
- 有一个正在运行的程序调用了开启进程的接口
- 一个批处理作业开始
- 系统初始化
销毁:
- 任务完成
- 强制结束 windows:taskkill/linux:kill
- 程序异常
进程和程序
程序是一堆代码存放在文件中
进程就是程序中的代码从硬盘读入内存后产生的
进程是由程序产生的
一个程序可以产生多个进程,每一个进程都有一个唯一的PID
进程的层次结构
在linux中进程具备父子关系,是一个树状结构,可以相互查找到对方
在windows中,没有层级关系,父进程可转让子进程的句柄
父进程与子进程:例如qq打开了浏览器,qq就是父进程,浏览器就是子进程
PID和PPID
PID是当前进程的编号
PPID是当前程序的父进程的编号
我们运行py文件的时候其实是运行python解释器,所以运行py文件的父进程就是python解释器
import os
os.getpid()
os.getppid()
python如何使用多进程
1. 导入multiprocessing中的Process类 实例化这个类 指定要执行的任务target
import os
from multiprocessing import Process
def task():
print("this is sub process")
print(f"sub process id {os.getpid()}" )
if __name__ == '__main__':
p = Process(target=task)
p.start()
print("this is parent process")
print(f"parent process is: {os.getpid()}")
print("over")
linux和windows开启进程的方式区别
linux会将父进程的内存数完整复制一份给子进程
windows会导入父进程的代码,从新执行一遍,来获取需要处理的任务,所以在编写代码时如果是windows,需要把开启进程的代码放在main判断中
2. 导入multiprocessing中的Process类 继承这个类 覆盖run方法 将要执行的任务放入run中开启进程是会自动执行该函数
from multiprocessing import Process
import os
class Downloader(Process):
def __init__(self,url,size,name):
super().__init__()
self.url = url
self.size = size
self.name = name
def run(self):
print(os.getpid())
pass
if __name__ == '__main__':
m = Downloader()
m.start()
print("parent over",os.getpid())
进程之间内存相互隔离
from multiprocessing import Process
import os,time
a = 257
def task():
global a
print("2",a,id(a))
a = 200
if __name__ == '__main__':
p = Process(target=task)
p.start()
time.sleep(4)
print(a)
join函数
就是让主进程等待子进程运行完再执行
from multiprocessing import Process
import time
def task1(name):
for i in range(10000):
print(f'{name} run')
def task2(name):
for i in range(100):
print(f'{name} run')
if __name__ == '__main__':
p1 = Process(target=task1,args=("p1",))# args 是给子进程传递的参数 必须是元组
p1.start()
p2 = Process(target=task2,args=("p2",))
p2.start()
p2.join()
p1.join()
print("over")
进程对象的常用属性
if __name__ == '__main__':
p = Process(target=task,name="老司机进程")
p.start()
# p.join()
# print(p.name)
# p.daemon #守护进程
# p.join()
# print(p.exitcode) # 获取进程的退出码 就是exit()函数中传入的值
# print(p.is_alive()) # 查看进程是否存活
# print("zi",p.pid) # 获取进程id
# print(os.getpid())
# p.terminate() #终止进程 与strat 相同的是 不会立即终止,因为操作系统有很多事情要做
# print(p.is_alive())
僵尸进程和孤儿进程
孤儿进程:当父进程已经结束,而子进程还在运行,子进程就成为孤儿进程,孤儿进程有其存在的意义,没有不良影响,孤儿进程会被操作系统接管
僵尸进程:当一个进程已经结束了,但是他仍然还有一些数据存在,此时称之为僵尸进程
在linux中,有这么一个机制,父进程无论什么时候都可以获取到子进程的一些数据;子进程任务执行完毕后,确实结束了但是仍然保留了一些数据,目的是为了让父进程能够获取这些信息;linux中,可以调用waitpid来彻底清除子进程的残留信息
python中已经封装了处理僵尸进程的操作