进程微讲解
进程微讲解
1.代码创建进程
# 创建进程的方式
我们前面已经讲过进程就是正在执行的代码或者说正在运行的程序,那么创建进程就有两种方式。第一种就是当我们用鼠标
双击桌面的一个应用图标时,就相当于在创建一个进程,我们知道当我们双击一个应用图标就是想要启动这个程序,双击之后就会控
制硬盘转动,把硬盘中的数据加载到内存中,然后CPU再执行内存中的任务,其实我们加载到内存中的就是代码,CPU执行任务其实就
是执行代码;第二种就是代码创建一个进程,其实还是CPU执行代码产生进程。创建进程的本质就是在内存空间中申请一块内存空间用
于运行相应的程序代码。
# 用代码创建进程的第一种方式
from multiprocessing import Process
import time
def task(name):
print(f'{name}刚刚开始')
time.sleep(3)
print(f'{name}已经结束了')
if __name__ == '__main__':
p = Process(target=task, args=('oscar',)) # 创建一个进程
p.start() # 告诉操作系统创建一个新的进程
print('主进程')
# 用代码创建进程的第二种方式
from multiprocessing import Process
import time
class MyProcess(Process):
def __init__(self,name):
self.name = name
super().__init__()
def run(self):
print('刚刚来的',self.name)
time.sleep(3)
print('又走了',self.name)
if __name__ == '__main__':
p = MyProcess('oscar')
p.start()
print('主进程')
# 不同系统下代码创建进程的方式
windows系统:创建进程是以导入模块的方式进行的,所以创建进程的代码必须写在__main__子代码中,否则就会直接报错,一直在无
限的创建进程。
linux、mac系统:创建进程直接拷贝一份源码然后执行即可,不需要写在__main__子代码中。

2.join方法
# 1.join方法的含义及简单用法
1.join方法可以让主进程代码等待子进程代码运行完毕之后在执行。我们在上述创建进程的时候,执行打印的顺序是先打印
的主进程的,然后在打印子进程的,我们使用join方法之后就可以先执行子进程的代码,子进程的代码执行完毕之后在执行主进程的代码。
2.简单用法
from multiprocessing import Process
import time
def task(name):
print(f'{name}刚刚开始')
time.sleep(3)
print(f'{name}已经结束了')
if __name__ == '__main__':
p = Process(target=task, args=('oscar',)) # 创建一个进程
p.start() # 告诉操作系统创建一个新的进程
p.join() # 执行完子进程的代码之后在执行下面的主进程代码
print('主进程')
# 2.进一步理解进程的等待过程
from multiprocessing import Process
import time
def task(name,n):
print(f'{name}刚刚来')
time.sleep(n)
print(f'{name}又走了')
if __name__ == '__main__':
p1 = Process(target=task,args=('oscar',1))
p2 = Process(target=task,args=('kevin',4))
p3 = Process(target=task,args=('jason',3))
start_time = time.time()
p1.start()
p2.start()
p3.start()
p1.join()
p2.join()
p3.join()
end_time = time.time() - start_time
print(end_time) # 4.111866235733032
# start_time = time.time()
# p1.start()
# p1.join()
# p2.start()
# p2.join()
# p3.start()
# p3.join()
# end_time = time.time() - start_time
# print(end_time) # 8.277277231216431
'''当start与join不是交替执行的时候,总耗时就是最长的一个时间,因为当它在等待的时候其他的也在运行,而当start与join
交替执行的时候,总耗时就是所有时间的综合,因为交替执行的情况下,一个进程在等待,其他的也不会执行,等这一个执行完之后
才会执行下一个'''
3.进程间数据默认隔离
我们知道当我们创建一个进程的时候它是有一个独自的内存空间的,这些内存空间是彼此互不干扰的,也就是相互隔离,谁
也不碰谁,默认隔离,但是也是可以通过一些手段打破隔离的,这里我们先不讲,我们先验证一下进程间的数据是不是默认隔离的。
from multiprocessing import Process
s = 1
def task():
global s # 局部修改全局不可变类型
s = 2
if __name__ == '__main__':
p = Process(target=task)
p.start()
p.join() # 确保子进程代码运行结束在打印s
print(s) # 1
4.进程对象属性和方法
# 查看进程号
1.在cmd命令行中
Windows:tasklist
mac:ps -ef
2.在pycharm中
(1).current_process函数
from multiprocessing import Process,current_process
print(current_process().pid) # 14320
(2).os模块
from multiprocessing import Process
import os
def task():
print(os.getpid()) # 获取当前进程的进程号
print(os.getppid()) # 获取当前进程的父进程号
if __name__ == '__main__':
p = Process(target=task)
p.start()
p.join()
# 获取进程号的用处之一就是可以通过代码的方式管理进程
1.杀死子进程
(1).cmd命令行中
windows系统:tsskkill -PID 进程号 -f
mac系统:kill -9 PID
(2).pycharm中
terminate()
2.判断子进程是否存活
is_alive() # 子进程存活返回True,不存活就返回False
# 通过代码管理进程
from multiprocessing import Process
import os
import time
def task():
print(os.getpid())
print(os.getppid())
if __name__ == '__main__':
p = Process(target=task)
p.start() # 创建新的进程需要时间(申请空间、导入数据)
p.terminate() # 杀死进程也需要时间(销毁数据、回收空间)
time.sleep(0.1) # 所以我们给主进程加一个等待时间
print(p.is_alive()) # False
print('主进程')
5.僵尸进程与孤儿进程
# 僵尸进程
主进程默认需要等待子进程结束才会结束,所有的子进程在运行结束之后都会变成僵尸进程,意思就是虽然已经执行结
束了,但是还没有回收,还保留着pid和一些运行过程中的记录,便于主进程查看,只是短时间的保存,然后这些信息就会被主进
程回收,子进程也就彻底结束了。
结束僵尸进程的两种方式:
1.主进程正常结束
2.调用join方法
# 孤儿进程
子进程存活着,父进程意外死亡,这时候子进程就是孤儿进程,这些孤儿进程就会被操作系统自动接管。
6.守护进程
当一个进程守护另一进程,被守护的进程一旦死亡,那么守护该进程的进程也就会立刻死亡。
from multiprocessing import Process
import time
def task(name):
print(f'{name}正常活着')
time.sleep(3)
print(f'{name}正常死亡')
if __name__ == '__main__':
p = Process(target=task, args=('tom',))
# 必须写在start前面
p.daemon = True # 将子进程设置为守护进程:主进程一结束,子进程立马结束
p.start()
print('主进程死亡') # 只打印了主进程的信息,然后主进程就结束了,所以子进程立马死亡,不会执行
7.互斥锁
在讲'互斥锁'之前我们现讲一个现象,过年回家的时候大家一定都抢过车票吧,有时候手机上明明显示有票,但是点击
购买的时候却提示没有票了,然后我们回去查看车票的时候,发现确实没有车票了。其实这个现象的原因是这样的,当我们进入这
个页面的时候,程序会反馈给我们余票情况,当我们在查看余票的时候,其实其他人已经在购买当中了,所以呢只要你不刷新或者
不点击下一步,那么那个页面的数据是不会改变的,但是只要你一刷新,就会刷新数据,更新为当前的余票状况。
# 代码模拟抢票
import json
from multiprocessing import Process
import time
import random
# 查票
def search(name):
with open(r'piao.json','r',encoding='utf8') as f:
data = json.load(f)
print('%s查询当前余票为:%s'%(name,data['num']))
# 买票
def buy(name):
# 买票之前再次查票,因为期间其他人可能已经把票买走了
with open(r'piao.json', 'r', encoding='utf8') as f:
data = json.load(f)
time.sleep(random.randint(1,3)) # 模仿手机网络延迟
# 判断是否还有余票
if data['num'] > 0:
data['num'] -= 1
with open(r'piao.json', 'w', encoding='utf8') as f1:
json.dump(data,f1)
print(f'{name}抢票成功')
else:
print(f'{name}抢票失败')
def run(name):
search(name)
buy(name)
# 模拟多人同时抢票
if __name__ == '__main__':
for i in range(1,10):
p = Process(target=run,args=(i,))
p.start()
'''上述模拟抢票的代码是有问题的,只有一张票每个人都抢到了,这是不符合实际的,所以呢就用到了互斥锁。'''
# 互斥锁
当多个进程操作同一份数据的时候会造成数据的错乱,这个时候就需要加锁处理(互斥锁),将并发改为串行,虽然牺
牲了效率但是保证了数据的安全。但是呢互斥锁并不能轻易使用,容易造成死锁现象,互斥锁只在处理数据的部分加锁,不能什
么地方都加锁,否则就严重影响到了程序的效率。
所以呢我们就可以对上述代码做一下优化,查票可以一次性给所有人看,但是买票的时候必须一个一个来。
# 只需要这两处优化即可,就可以实现模拟抢票了
from multiprocessing import Process,Lock
def run(name,mutex):
search(name)
# 把买掉环节变成串行
mutex.acquire() # 抢锁
buy(name)
mutex.release() # 放锁
# 模拟多人同时抢票
if __name__ == '__main__':
mutex = Lock() # 互斥锁在主进程中产生一把,交给多个子进程使用
for i in range(1,10):
p = Process(target=run,args=(i,mutex))
p.start()
这里是IT小白陆禄绯,欢迎各位大佬的指点!!!
