并发编程
并发即同时发生,并发编程指程序中的多个任务同时被处理,其基于多道技术。
并发与并行
并发指的是多个事件同时发生,也称为伪并行(并发事实上是交替进行,如果切换速度足够快,那么就可以让人觉得是在同时发生)
并行指的是多个事件同时进行
阻塞与非阻塞
阻塞:程序遇到I/O操作或是sleep,导致后续代码不能被CPU执行的状态。
非阻塞:代码正常被CPU执行。
多道技术:
空间复用:内存分成几个部分,不同部分运行不同程序,彼此之间独立。 时间复用:当一个程序执行非计算操作(如I/O,等待用户输入;或操作系统强制切换),将CPU切到另一个程序进行计算操作。
进程的三种状态:就绪态,运行态,阻塞态
多道技术会在进程执行时间过长或遇到I/O时自动切换到其他进程,原进程被剥夺CPU执行权,需要重新进入就绪态后才能等待执行。
进程:正在运行的程序就是进程,是一种抽象的概念。 进程依赖于操作系统,一个进程代表着一份资源(内存等),进程间内存相互独立,一个程序可以有多个进程。
# 子进程中的数据修改,不会影响父进程,即子进程和父进程之间相互独立(子进程在运行前会拷贝父进程的环境,所以在子进程运行前要初始化好父进程的环境)
import time
a = 100
def task():
global a
a = 0 # 子进程中的全局变量a已经变为0,但是父进程中的全局变量a,子进程无权修改。
print("子进程",a)
if __name__ == '__main__':
p = Process(target=task)
p.start()
time.sleep(1) # 注意此处的时间停顿,此处引出join函数(子进程全部结束再执行父进程),详见最后。
print("主程序",a)
输出结果为:
子进程 0
主程序 100
与上面的做一下对比:
两次结果的差异在于:子进程需要载入才能运行,这时候CPU可以进行主程序的其他内容操作。如果主程序睡眠,那么睡眠时间就够子程序执行完了。所以在输出顺序上存在差异。
import time
a = 100
def task():
global a
a = 0
print("子进程",a)
if __name__ == '__main__':
p = Process(target=task)
p.start()
print("主程序",a)
输出结果为:
主程序 100
子进程 0
PID与PPID
PID:操作系统给每个进程编号,这个编号就是PID
PPID:当进程A开启了另一个进程B,A称为B的父进程,B称为A的子进程,操作系统可以看做是所有主进程的父进程。
import os
while True:
print("haha")
print("self",os.getpid()) # 获取self的进程ID,ID号由操作系统分配
print("parent",os.getppid()) # 第一个p指的是parent,即父进程的ID
break
# 每次的ID号都会不同,pid是当前执行程序,如果这段代码放到pycharm中运行,ppid就是pycharm的编号,如果这段代码放到cmd中运行,ppid就是cmd的编号。
孤儿进程和僵尸进程:
孤儿进程:父进程已经终结,但子进程仍在运行,无害,操作系统会负责回收处理等操作。
僵尸进程:子进程执行完毕,残留一些信息(如进程id等)。但父进程没有处理残留信息,
导致残留信息占用内存,有害。
了解
操作系统的两个核心作用: 1、屏蔽了复杂繁琐的硬件接口,为程序提供了简单易用的系统接口 2、将应用程序对硬件资源的竞争变成有序的使用。
操作系统与普通应用程序的区别: 1、操作系统不能轻易被用户修改 2、操作系统源代码量巨大,仅内核通常就在五百万行以上 3、长寿,一旦完成不会轻易重写。
python中开启子进程的两种方式
方式1:
实例化Process类
from multiprocessing import Process
import os
def task():
print("self",os.getpid()) # 自己的id
print("parent",os.getppid()) # 父进程的id,此处的父进程是下面的p的那个进程(由p启动)
print("task run")
# win下创建子进程时,子进程会将父进程的代码加载一遍,导致重复创建子进程
# 所以一定要将创建子进程的代码放到main的下面。
if __name__ == '__main__':
print("self",os.getpid()) # 当前执行内容的id
print("parent",os.getppid()) # 通过pycharm执行,所以此处的父进程id是pycharm的id
p = Process(target=task, name="子进程") # 主进程启动子进程,目标子进程为task(注意此处仅为函数名)。创建一个表示进程的对象,并不是真正的创建进程,Process是一个类,他还有很多参数和方法,具体可以ctrl点进去查看。
p.start() # 向操作系统发送请求,操作系统会申请内存空间,然后把父进程的数据拷贝给子进程,作为子进程的初始状态(但你爹永远是你爹(父进程),接产大夫(操作系统)不会成为隔壁老王)
方式2:
继承Process类 并覆盖run方法,子进程启动后会自动执行run方法。
这种方法可以自定义进程的属性和行为,拓展对象的功能。
class MyProcess(Process): # 继承Process类
def __init__(self,url):
super().__init__()
self.url = url
def run(self): # 覆盖run方法
print("正在下载文件。。。",self.url)
print("runrunrun")
if __name__ == '__main__':
p = MyProcess("www.baidu.com")
p.start() # 实例化了Process对象,start自动调用run。
需要注意的是 在windows下 开启子进程必须放到__main__
下面,因为windows在开启子进程时会重新加载所有的代码造成递归
join函数
调用start函数后的操作就由操作系统来玩了,至于何时开启进程,进程何时执行,何时结束都与应用程序无关,所以当前进程会继续往下执行,join函数可以让父进程等待子进程结束后再执行。(即提高子进程优先级)
案例1:
from multiprocessing import Process
import time
x=1000
def task():
time.sleep(3)
global x
x=0
print('儿子死啦',x)
if __name__ == '__main__':
p=Process(target=task)
p.start()
p.join() # 让父亲在原地等,子进程执行完毕才会执行下面的代码。
print(x)
案例2:
from multiprocessing import Process
import time
def task(num):
print("我是%s进程" %num)
time.sleep(2) # 排队睡,都是就绪态,输出结果可能是乱序态
print("我是%s进程" %num)
if __name__ == '__main__':
start_time = time.time()
ps = []
for i in range(5):
p = Process(target=task,args=(i,)) # 注意此处传参方式!元组形式
p.start()
ps.append(p)
for p in ps:
p.join()
print("over")
print(time.time()-start_time)
输出结果:
我是0进程
我是1进程
我是2进程
我是0进程
我是3进程
我是1进程
我是4进程
我是2进程
我是3进程
我是4进程
over
5.9241626262664795 #正常应该是2秒多,我电脑太垃圾了,所以卡的接近6秒。
也可以写成:
from multiprocessing import Process
import time
def task(num):
print("我是%s进程" %num)
time.sleep(2) # 排队睡,都是就绪态,输出结果可能是乱序态
print("我是%s进程" %num)
if __name__ == '__main__':
start_time = time.time()
for i in range(5):
p = Process(target=task,args=(i,))
p.start()
p.join() # 提高子进程优先级,CPU优先处理子进程,父进程放到后面
print("over")
print(time.time()-start_time)
输出结果为:
我是0进程
我是1进程
我是2进程
我是0进程
我是3进程
我是1进程
我是4进程
我是2进程
我是3进程
我是4进程
over
5.880282878875732
如果改写成这样:
from multiprocessing import Process
import time
def task(num):
print("我是%s进程" %num)
time.sleep(2) # 排队睡,都是就绪态,输出结果可能是乱序态
print("我是%s进程" %num)
if __name__ == '__main__':
start_time = time.time()
for i in range(5):
p = Process(target=task,args=(i,))
p.start()
p.join() # 提高子进程优先级,CPU优先处理子进程,父进程放到后面,此处为只有当前子进程结束后,才会继续循环。可以理解为for循环还是属于父进程的内容。
print("over")
print(time.time()-start_time)
输出结果为:
我是0进程
我是0进程
我是1进程
我是1进程
我是2进程
我是2进程
我是3进程
我是3进程
我是4进程
我是4进程
over
14.754556894302368 #正常应该是10秒多一点,我这戴尔垃圾太卡,果然是傻多戴。
Process对象常用属性
from multiprocessing import Process
def task(n):
print('%s is runing' %n)
time.sleep(n)
if __name__ == '__main__':
start_time=time.time()
p1=Process(target=task,args=(1,),name='任务1') # 注意传参方式
p1.start()
print(p1.pid) # 获取进程编号
print(p1.name) # 获取进程属性
p1.terminate() # 结束进程
p1.join() # 提高进程优先级
print(p1.is_alive()) # 判断进程是否存在
print('主进程')