No.33
今日概要
- 守护进程
- Process类
- 面向对象方式开启进程
- 锁
- 进程之间的通信
内容回顾
线程
- 线程是进程的一部分,每个进程中至少有一个线程。
- 能被CPU调度的最小单位
- 数据共享
- 一个进程中的多个线程可以共享这个进程的数据
- 开销小
- 线程的创建、销毁、切换所需开销远远小于进程
multiprocessing模块
- Process类
- 如何创建一个进程对象 →
Process( target=函数名,args=(参数,) )
- 对象和进程之间的关系
- 进程对象和进程并没有直接关系
- 进程对象只是存储了和进程相关的内容
- 此时此刻操作系统还没有接到开启进程的指令
- 对象和进程之间的关系
- 如何开启一个进程
- 对过
对象.start()
就开启了一个进程 → 相当于给了操作系统一条指令 - start方法的非阻塞和异步特点
- 我们既不等待这个进程开启,也不等待操作系统给我们的响应
- 我们只是负责通知操作系统去开启一个进程
- 当操作系统开启了一个子进程之后,主进程和子进程的代码完全异步
- 对过
- 父进程和子进程之间的关系
- 父进程会等待子进程结束之后才结束
- 目的是为了回收子进程的资源
- 不同操作系统中进程开启的方式
- windows
- 通过再一次执行(模块导入)父进程文件中的代码来获取父进程中的数据。
- 不希望被子进程执行的代码就写在
if __name__ == '__main__'
下。
- ios/linux
- 直接写即可,不用放在
if __name__ == '__main__'
下。
- 直接写即可,不用放在
- windows
- 如何确认一个子进程执行完毕
- join方法 →
对象.join
- 阻塞主进程,直到子进程执行完毕。
- 开启多个子进程,等待所有子进程结束
- 创建列表储存所有的进程对象
- 循环列表中的进程对象来使用join方法
- join方法 →
- 如何创建一个进程对象 →
内容详细
1.守护进程
有一个参数可以将一个子进程设置为一个守护进程
# 守护进程随着主进程的代码结束而结束
import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
if __name__ == '__main__':
p = Process(target=son1)
p.daemon = True # 把子进程设置成了守护进程
p.start()
time.sleep(2)
import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
def son2():
for i in range(5):
print('in son2')
time.sleep(1)
if __name__ == '__main__':
p1 = Process(target=son1)
p1.daemon = True
p.start()
p2 = Process(target=son2)
p2.start()
time.sleep(2)
2.Process类
import time
from multiprocessing import Process
def son1():
while True:
print('is alive')
time.sleep(0.5)
if __name__ == '__main__':
p = Process(target=son1)
p.start() # 异步非阻塞
print(p.is_alive()) # 判断子进程是否处于运行中
time.sleep(1)
p.terminate() # 异步非阻塞:强制结束一个子进程
print(p.is_alive()) # True,因为操作系统还没来得及关闭
time.sleep(0.01)
print(p.is_alive()) # False,操作系统已经响应了关闭进程的需求
什么是异步非阻塞?
- terminate 是一个非常典型的异步非阻塞应用场景
3.面向对象方式开启进程
import os
from multiprocessing import Process
class MyProcess(Process): # 必须继承Process类
def run(self): # 必须有run方法
print(os.getpid(), os.getppid())
if __name__ == '__main__':
mp = MyProcess()
mp.start()
print('main:', os.getpid())
import time
from multiprocessing import Process
class MyProcess1(Process):
def run(self):
for i in range(5):
print('子进程要执行的代码封装进run方法中')
class MyProcess2(Process):
def run(self):
while True:
print('自定义类中必须有run方法')
time.sleep(0.2)
if __name__ == '__main__':
mp1 = MyProcess1()
mp1.start()
mp2 = MyProcess2()
mp2.daemon = True
mp2.start()
print('自定义类必须继承Process类')
time.sleep(1)
import time
from multiprocessing import Process
class MyProcess(Process):
def __init__(self, x, y):
self.x = x
self.y = y
super().__init__()
def run(self):
print(self.x, self.y)
for i in range(5):
print('666')
time.sleep(0.2)
if __name__ == '__main__':
mp = MyProcess(1, 2)
mp.start()
print('参数传递')
import os
from multiprocessing import Process
def func(arg):
print(arg)
class MyProcess(Process):
def __init__(self, target, args=()):
super().__init__(target=target, args=args)
if __name__ == '__main__':
mp = MyProcess(func, ('必须是元组',))
mp.start()
print('面向对象的方式开启子进程')
Process类
-
开启进程的方式
-
面向函数
def 函数名(): '要在子进程中执行的代码' p = Process(target=函数名, args=(参数,)) p.start()
-
面向对象
class 类名(Process): def __init__(self, 参数1, 参数2): '如果子进程不需要参数则不写' self.a = 参数1 self.b = 参数2 super().__init__() def run(self): '要在子进程中执行的代码' # 创建进程对象 p = 类名(参数1, 参数2) # Process类提供的操作进程的方法 p.start() '开启子进程,异步非阻塞' p.terminate() '结束子进程,异步非阻塞' p.join() '等子进程结束,同步阻塞' p.isalive() '获取当前子进程的状态' daemon = True '设置为守护进程,永远在主进程的代码执行结束之后自动结束'
-
4.锁
在一个并发的场景下,涉及修改共享数据资源时,则需要加锁来维护数据的安全。
import time
import json
from multiprocessing import Process, Lock # 导入Lock类
def search_ticket(user):
with open('test', 'r', encoding='utf-8') as f:
dic = json.load(f)
print('%s查询结果: %s张余票' % (user, dic['count']))
def buy_ticket(user, lock):
'with lock:' # with语句加锁
lock.acquire() # 给这段代码加锁
time.sleep(0.02)
with open('test') as f:
dic = json.load(f)
if dic['count'] > 0:
print('%s买到票' % (user,))
dic['count'] -= 1
else:
print('%s没买到票' % (user,))
time.sleep(0.02)
with open('test', 'w') as f:
json.dump(dic, f)
lock.release() # 给这段代码解锁
def task(user, lock):
search_ticket(user)
buy_ticket(user, lock)
if __name__ == '__main__':
lock = Lock()
lst = []
for i in range(1, 11):
p = Process(target=task, args=('user%s' % i, lock))
p.start()
lst.append(p)
for i in lst:
i.join()
with open('test', 'w') as f:
dic = {"count": 1}
json.dump(dic, f)
import time
import json
from multiprocessing import Process, Lock
def search_ticket(user):
with open('test', 'r', encoding='utf-8') as f:
dic = json.load(f)
print('%s查询结果: %s张余票' % (user, dic['count']))
def buy_ticket(user):
time.sleep(0.02)
with open('test') as f:
dic = json.load(f)
if dic['count'] > 0:
print('%s买到票' % (user,))
dic['count'] -= 1
else:
print('%s没买到票' % (user,))
time.sleep(0.02)
with open('test', 'w') as f:
json.dump(dic, f)
def task(user, lock):
search_ticket(user)
with lock: # 推荐with语句加锁,自带异常处理功能。
buy_ticket(user)
if __name__ == '__main__':
lock = Lock()
lst = []
for i in range(1, 11):
p = Process(target=task, args=('user%s' % i, lock))
p.start()
lst.append(p)
for i in lst:
i.join()
with open('test', 'w') as f:
dic = {"count": 1}
json.dump(dic, f)
同步存在的意义
- 数据的安全性
- 在数据安全的基础上,再考虑效率问题。
总结
- 步骤
- 先在主进程中实例化
lock = Lock()
- 再把实例化对象传给子进程
- 在子进程中对需要加锁的代码进行
with lock
with lock
相当于lock.acquire()
和lock.release()
- 先在主进程中实例化
- 应用场景
- 多进程共享数据资源(文件,数据库)
- 需要对共享资源进行修改、删除操作
- 加锁之后能过够保证数据的安全性,但是也降低了程序的执行效率。
5.进程之间的通信
- IPC(inter process communication)进程之间的通信
- 通过文件实现
- 使用别人封装好的功能
- Queue 对列
- 从
multiprocessing模块
中导入Queue
实现 Queue
基于socket模块
、pickle模块
、Lock模块
实现
- 从
- Pipe 管道
- 没有锁,不安全
Pipe
基于socket模块
、pickle模块
实现
- Queue 对列
- 使用别人封装好的功能
- 通过文件实现
# 进程之间的数据隔离
from multiprocessing import Process
n = 100
def func():
global n
n -= 1
print(n)
if __name__ == '__main__':
lst = []
for i in range(3):
p = Process(target=func)
p.start()
lst.append(p)
for p in lst:
p.join()
print('main',n)
结果:
99
99
99
main 100
from multiprocessing import Queue, Process
def func(exp, q):
ret = eval(exp)
q.put(ret) # 储存结果,先进先出。
if __name__ == '__main__':
q = Queue() # 创建对象
Process(target=func,args=('1+2+3', q)).start() # 传入参数
print(q.get()) # 获取结果
# Queue内部基于文件家族socket、pickle、Lock实现。
from multiprocessing import Queue
q = Queue()
q.put(1)
q.put(2)
q.put(3)
print(q.get())
print(q.get())
print(q.get())
print(q.get()) # 在队列为空的时候会发生阻塞
from multiprocessing import Queue
q = Queue(2) # 设置队列大小
q.put(1)
q.put(2)
print('显示')
q.put(3) # 当队列为满的时候向队列中放数据,会阻塞:不报错且不丢失数据。
print('不显示')
import queue
from multiprocessing import Queue
q = Queue(2)
q.put(1)
q.put(2)
print('显示')
try:
q.put_nowait(3) # 当队列为满的时候用该方法放数据,不阻塞:系统会报错且会丢失数据。
except queue.Full:
pass
print('不显示')
print(q.get())
print(q.get())
print(q.get()) # 阻塞
import queue
from multiprocessing import Queue
q = Queue(2)
q.put(1)
q.put(2)
print('显示')
try:
q.put_nowait(3)
except queue.Full:
pass
print('不显示')
print(q.get())
print(q.get())
try:
print(q.get_nowait()) # 队列为空的时候,不阻塞:系统会报错。
except queue.Empty:
pass