20190914
并发编程知识的整理
操作的系统的历史介绍
第一代计算机(1940~1955):真空管和插件板
第二代计算机(1955~1965):晶体管和批处理系统
第三代计算机(1965~1980):集成电路芯片和多道程序设计
**多道技术 **:多道技术中的多道指的是多个程序,多道技术的实现是为了解决多个程序竞争或者说共享同一个资源(比如cpu)的有序调度问题,解决方式即多路复用,多路复用分为时间上的复用和空间上的复用。
①空间上的复用:将内存分为几部分,每个部分放入一个程序,这样,同一时间内存中就有了多道程序(共用一个内存条,多个进程互相隔离,物理级别隔离,程序与程序之间互不干扰)
②时间上的复用:当一个程序在等待I/O时,另一个程序可以使用cpu,如果内存中可以同时存放足够多的作业,则cpu的利用率可以接近100%,类似于我们小学数学所学的统筹方法。(操作系统采用了多道技术后,可以控制进程的切换,或者说进程之间去争抢cpu的执行权限。这种切换不仅会在一个进程遇到 io 时进行,一个进程占用cpu时间过长也会切换,或者说被操作系统夺走cpu的执行权限)共用一个cpu
分时操作系统:
多个联机终端+多道技术
串行:一个程序完完整整的执行完才能运行下一个程序
并发:看起来像在同时运行(单核),实际上也是串行,区别在于它不一定执行完整,中途在不停地切换
并行:同时执行多个程序
程序:躺在硬盘上的文件
进程:一个执行的程序,是一系列资源的总和
进程与程序区别:进程是正在进行的一个过程或者说一个任务,而程序仅仅仅只是一堆代码。
需要强调的是:同一个程序执行两次,那也是两个进程,比如打开暴风影音,虽然都是同一个软件,但是一个可以播放苍井空,一个可以播放饭岛爱。
进程的三个基本状态
进程执行时的间断性,决定了进程可能具有多种状态。事实上,运行中的进程可能具有以下三种基本状态。
就绪状态(Ready):
进程已获得除处理器外的所需资源,等待分配处理器资源;只要分配了处理器进程就可执行。就绪进程可以按多个优先级来划分队列。例如,当一个进程由于时间片用完而进入就绪状态时,排入低优先级队列;当进程由I/O操作完成而进入就绪状态时,排入高优先级队列。
运行状态(Running):
进程占用处理器资源;处于此状态的进程的数目小于等于处理器的数目。在没有其他进程可以执行时(如所有进程都在阻塞状态),通常会自动执行系统的空闲进程。
阻塞状态(Blocked):
由于进程等待某种条件(如I/O操作或进程同步),在条件满足之前无法继续执行。该事件发生前即使把处理器资源分配给该进程,也无法运行
尽量减少阻塞状态可以提升我们程序运行的效率
并发 = 保存状态+切换
开启子进程 申请新的内存空间 把父进程的所有代码完整拷贝一份过去
# from multiprocessing import Process
# def task(x):
# pass
#
# if __name__ == '__main__':
# p = Process(target=task,args=(45,))
# p.start()
第二种开启子进程的方式(不常用)
# from multiprocessing import Process
#
#
# class Xx(Process):
# def __init__(self,x):
# super().__init__()
# self.x = x
# def run(self):
# pass
#
#
# p = Xx()
# p.start()
僵尸进程:父进程的子进程结束的时候父进程没有wait()情况下子进程会变成僵尸进程。(父进程一直不死不停造子进程并且不回收僵尸进程有害)
父进程等着所有的子进程结束才会结束。
孤儿进程(无害)
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
情况1 无害
父进等着子进程都死,回收僵尸进程。
情况2 无害
父进程死了,子进程活着,都要被init进程接管并且回收。
情况3 有害
父进程一直不死,造成了大量僵尸进程。占用了大量的pid号
pid号是有限的。
解决方案:
最直接的办法就是杀死父进程 。
20190915
Process中join方法
join:阻塞住主进程再等待子进程结束,然后再往下执行,
具体代码如下:
from multiprocessing import Process
import time
def foo():
time.sleep(4)
print('进程 start ')
time.sleep(5)
print('进程 end ')
if __name__ == '__main__':
p = Process(target=foo)
p.start() #
p.join(3) # 阻塞住主进程再等待子进程结束,然后再往下执行,(了解的是:内部会待用wait())
print('主')
p.join(3):括号内的数字代表阻塞的时间
多个join的情况,代码如下
from multiprocessing import Process
import time
def task(n):
print('子进程 开始')
time.sleep(n)
print('子进程 结束')
if __name__ == '__main__':
start_time = time.time() # 记录开始时间
p1 = Process(target=task,args=(2,))
p2 = Process(target=task,args=(4,))
p3 = Process(target=task,args=(6,))
p1.start()
p2.start()
p3.start()
# 换顺序也是一样的也是按照时长最长的那个计算。
p1.join() # 等待2s
p2.join() # 等待2s
p3.join() # 等待2s
end_time = time.time() # 记录结束时间
print(end_time-start_time) # 计算时间差
print('主线程')
子进程 开始
子进程 开始
子进程 开始
子进程 结束
子进程 结束
子进程 结束
7.566513776779175
主线程
join串行的情况
from multiprocessing import Process
import time
def task(n):
print('子进程 开始')
time.sleep(n)
print('子进程 结束')
if __name__ == '__main__':
start_time = time.time() # 记录开始时间
p1 = Process(target=task,args=(2,))
p2 = Process(target=task,args=(4,))
p3 = Process(target=task,args=(6,))
p1.start()
p1.join()
p2.start()
p2.join()
p3.start()
p3.join()
end_time = time.time() # 记录结束时间
print(end_time-start_time) # 计算时间差
print('主线程')
'''输出:
子进程 开始
子进程 结束
子进程 开始
子进程 结束
子进程 开始
子进程 结束
15.407774925231934
主线程
'''
# ps:反而不如不开进程,正常调用三次来的快。
精炼代码:
from multiprocessing import Process
import time
def task(n):
print('子进程 开始')
time.sleep(n)
print('子进程 结束')
if __name__ == '__main__':
start_time = time.time() # 记录开始时间
task_list = []
for i in range(1,4):
p = Process(target=task,args=(i,))
p.start()
task_list.append(p)
print(task_list) # [<Process(Process-1, started)>, <Process(Process-2, started)>, <Process(Process-3, started)>]
for i in task_list:
i.join()
end_time = time.time() # 记录结束时间
print(end_time-start_time) # 计算时间差 4.764175891876221
print('主线程')
pid的用法
在当前进程查看当前进程pid
os.getpid()
current_process().pid
在当前进程查看子进程pid
子进程对象.pid()
在当前进程查看父进程pid
os.getppid()
from multiprocessing import Process,current_process
import time,os
def task():
print('子进程 start')
print('在子进程中查看自己的pid',current_process().pid) # 在子进程中查看自己的pid
print('在子进程中查看父进程的pid',os.getppid()) #
time.sleep(200)
print('子进程 end')
if __name__ == '__main__':
p = Process(target=task)
p.start()
print('在主进程查看子进程的pid',p.pid) # 一定要写在 start()之后
print('主进程的pid',os.getpid())
print('主进程的父进程pid',os.getppid())
print('主')
name的用法:
from multiprocessing import Process,current_process
def task():
print(current_process().name) # 在子进程查看自己的name
if __name__ == '__main__':
p = Process(target=task)
p2 = Process(target=task)
p3 = Process(target=task,name='rocky') # 已定义name属性为rocky
p.start()
p2.start()
p3.start()
print(p.name) #Process-1
print(p2.name) #Process-2
print(p3.name) #rocky
is_alive用法:
from multiprocessing import Process,current_process
import time
def task():
print('子进程开始')
time.sleep(1)
print('子进程结束')
if __name__ == '__main__':
p = Process(target=task)
p.start()
# p.terminate() #发送一个指令给操作系统但是不会立即结束子进程
# time.sleep(1)
print(p.is_alive()) # True 判断子进程代码是否结束
time.sleep(3)
print(p.is_alive()) # False
守护进程:
主进程创建守护进程
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常:AssertionError: daemonic processes are not allowed to have children
什么情况适合用守护进程?
当主进程代码结束,该子进程再执行无意义的情况可以用守护进程。
具体代码如下
例一:主进程代码运行完,守护进程立即结束
from multiprocessing import Process
import time
def foo():
print('守护进程开始')
time.sleep(5)
print('守护进程开始')
if __name__ == '__main__':
p1 = Process(target=foo)
p1.daemon = True # 一定要凡在start之前,表示设置为一个守护进程。
p1.start()
print('主进程')
'''输出:
主进程
'''
# 守护进程一旦发现主进程代码运行完,立刻结束,并不会管自己的进程是否运行完
例二 主进程代码运行完后等在子进程运行阶段,线程不会参与守护
from multiprocessing import Process
import time
def foo():
print('守护进程开始')
time.sleep(5)
print('守护进程结束')
def task():
print('子进程开始')
time.sleep(3)
print('子进程开始')
if __name__ == '__main__':
p1 = Process(target=foo)
p2 = Process(target=task)
p1.daemon = True # 一定要凡在start 之前
p1.start()
p2.start()
# p2 = Process(target=task)
print('主进程')
'''输出:
主进程
子进程开始
子进程开始
'''
#分析 守护进程在运行完主进程最后一行代码就结束,但是主进程并没有结束,主进程在等待子进程运行结束.