04. 守护进程、互斥锁、队列

一、进程其他方法介绍

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
'''
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值