守护进程、互斥锁、队列
一、进程其他方法介绍
1.1 pid、is_alive、terminate
每个进程都有自己的id号——pid
from multiprocessing import Process, current_process
import time
import os
def task():
print('子进程运行')
print(current_process().pid)
print(os.getpid()) # 结果与上一行一样
print(current_process().is_alive()) # 查看进程是否仍在运行
time.sleep(1)
print('子进程结束')
if __name__ == '__main__':
p = Process(target=task,)
p.start()
print(p.pid)
p.terminate()
print(p.is_alive()) # True
time.sleep(0.1)
print(p.is_alive()) # False
print('主进程')
'''
运行结果:
3332
True
False
主进程
'''
p.terminate()刚执行命令的时候,由于系统的响应操作(杀死子进程p)需要时间大于主进程继续执行下一行的时间,所以紧接着的判断p.is_alive()依然是True,(也就是说当时子进程还没被杀死)
二、进程之间的数据是隔离的
子进程中的数据改变与主进程没有关系
from multiProcessing import Process
import time
age = 18
def task1():
global age
age = 80
print('子进程')
time.sleep(0.5)
print('子进程结束', age)
if __name__ == '__main__':
p = Process(target=task1)
p.start()
p.join() # 等待子进程执行完成
print('主进程的age', age) # 数据没有变,主进程中打印age和子进程中的age没有任何关系
'''
运行结果:
子进程
子进程结束 80
主进程的age 18
'''
三、守护进程
主进程创建守护进程:
守护进程会在主进程执行结束后终止运行(不管有没有运行完)
守护进程内无法再开启子进程,否则抛异常:AssertionError: daemonic processes are not allowed to have children
语法示例:
def task2():
print('子进程')
time.sleep(100)
print('子进程结束')
if __name__ == '__main__':
p1 = Process(target=task2)
p1.daemon = True # 一定写在子进程启动之前
p1.start()
time.sleep(2)
print('主进程结束')
补充:
主进程的父进程是pycharm
主进程z开了许多子进程, 不是每一个子进程都需要设置守护(看需求,需要就在子进程比如t开启之前加上t.daemon = True)
四、互斥锁
进程之间数据不共享,但是可以共享同一套文件系统,所以可以访问同一个文件或者使用同一个打印终端。
共享会带来竞争,从而导致错乱
使用加锁处理可以控制这一乱象
4.1 多个进程共享同一个打印终端
如果使用并发运行,效率高,但是竞争同一打印终端,带来打印错乱
办法:采用进程同步锁
由并发变成了串行,牺牲了运行效率,但是避免了竞争
from multiprocessing import Process, Lock
import os, time
def work(lock):
lock.acquire()
print('%s is running' % os.getpid())
time.sleep(2)
print('%s is done' % os.getpid())
lock.release()
if __name__ == '__main__':
lock = Lock()
for i in range(3):
p = Process(target=work, args=(lock,))
p.start()
4.2 多进程共享同一文件
模拟抢票(文件充当数据库)
数据文件ticket:(存的json格式数据)
{"ticket_count":1}
模拟抢票程序:
import json, time, random
from multiprocessing import Process, Lock
def search():
with open('ticket', 'r', encoding='utf-8')as f:
dic = json.load(f)
print(f'余票还有{dic.get("ticket_count")}张')
def buy():
with open('ticket', 'r', encoding='utf-8')as f:
dic = json.load(f)
print(f'余票还有{dic.get("ticket_count")}张')
time.sleep(random.randint(1, 3))
if dic.get('ticket_count') > 0:
dic['ticket_count'] -= 1
with open('ticket', 'w', encoding='utf-8')as p:
json.dump(dic, p)
print('购买成功!')
else:
print('购买失败!')
def task(mutex):
search()
# mutex.acquire()
# buy()
# mutex.release()
# 使用with语句可以代替上面的三行代码
with mutex:
buy()
if __name__ == '__main__':
mutex = Lock()
for i in range(10):
p = Process(target=task, args=(mutex,))
p.start()
加锁可以保证多个进程修改同一块数据,同一时间只有一个任务可以进行修改,即串行的修改
牺牲了速度,但是保证了数据安全
总结:
虽然可以使用文件共享数据实现进程间通信,但是有两个问题:
① 效率低(文件是硬盘上的数据)
② 需要自己加锁处理
五、队列
进程彼此之间互相隔离,要实现进程间通信(IPC),multiprocessing模块支持两种形式:队列和管道。
这两种方式都是使用消息传递的
5.1 优点
队列和管道都是将数据存放在内存中
队列是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来
我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程书目增多时,往往可以获得更好的可拓展性
5.2 创建队列的类——Queue
Queue([maxsize]):创建共享的进程队列,Queue是多进程安全的队列,可以使用Queue实现多进程之间的数据传递
maxsize是队列中允许最大项数,省略则无大小限制
方法介绍:
① q.put() 插入数据进队列中,(可选参数有blocked和timeout)
如果blocked为True(默认值),并且timeout为正值,该方法会阻塞timeout指定的时间,直到该队列有剩余的空间。
如果超时,抛出Queue.Full异常;如果blocked为False,但是该Queue已满,会立即抛出Queue.Full异常。
② q.get() 从队列中读取并且删除一个元素,同样的,get方法也有两个可选参数:blocked和timeout
③ q.get_nowait() 等同于q.get(False)
④ q.put_nowait() 等同于q.put(False)
⑤ q.empty() 调用该方法时,q为空则返回True,该结果不可靠,比如在返回True的过程中,队列又加入了项目
⑥ q.full() 判断q是否已满,结果同样不可靠,同上
⑦ q.qsize() 返回列表中目前的正确数量,结果同样不可靠
5.3 示例
from multiprocessing import Queue
q = Queue(5)
q.put(1)
q.put(2)
q.put(3)
q.put_nowait(4) # 不等待,直接存,如果存不进去就直接报错queue.Full
q.put(5, timeout=1) # 等1秒钟,如果存不进去就报错queue.Full
# q.put(6) # 多放值不会报错,但是会挂起程序,一直等队列有值被取走,然后放进6
print(q.get())
print(q.get())
print(q.get())
print(q.get_nowait()) # 直接取值,如果没有值可取直接报错_queue.Empty
print(q.get(timeout=1)) # 等1秒钟如果没有值可取就报错 _queue.Empty
# print(q.get()) # 多取值也不会报错,但是会挂起一直等队列中有值,然后取出
print(q.empty()) # True
print(q.full()) # False
5.4 总结
q = Queue(队列大小)
# 放值
q.put(xxx) # 放不进去就等着
q.put_nowait(xxx) # 队列满了放不进去就直接报错
# 取值
q.get() # 从对列头部取一个值,没有值可取就等着
q.get_nowait() # 从队列头部取值,如果没有值可取就报错
# 判断队列是否为空,是否为满
print(q.empty()) # 判断队列是否为空
print(q.full()) # 判断队列是否为满
5.5 进程之间通信
from multiprocessing import Process, Queue
import os
def task1(q):
print(f'进程1,id号为:{os.getpid()}')
q.put('hello')
def task2(q):
res = q.get()
print(f'进程2,id号为{os.getpid()}')
print(res)
if __name__ == '__main__':
q = Queue(5)
p1 = Process(target=task1, args=(q,))
p1.start()
p2 = Process(target=task2, args=(q,))
p2.start()
p2.join()
print(q.get()) # 取不到值就一直等着,主进程不关闭
'''
运行结果:
进程1,id号为:8132
进程2,id号为16460
hello
'''