进程知识点补充

进程间数据隔离

不同进程之间的数据都是默认互不干涉的,可以看成每个进程都在内存中有一个自己房间,彼此不干扰,但可以通过一些技术打破。

举例

from multiprocessing import Process

money = 999

def task():
    global money  # 局部修改全局不可变类型
    money = 666

if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    p.join()  # 确保子进程代码运行结束再打印money
    print(money)
    
"""
执行结果:
999
"""

进程对象属性和方法

获取进程号

在windows系统中,我们可以通过在cmd中输入tasklist命令查看所有进程。

image

代码中也有方法可以查看执行文件当前的进程号和父进程号。

current_process().pid可以获取当前进程号

from multiprocessing import Process, current_process

def task():
    print('子进程号:', current_process().pid)

if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    print('主进程号:', current_process().pid)

"""
执行结果:
主进程号: 8896
子进程号: 2312
"""

os模块中的getpid()可以获取当前进程号,getppid()可以获取当前进程的父进程号。

from multiprocessing import Process
import os

def task():
    print('子进程号:', os.getpid())
    print('子进程的父进程号:', os.getppid())

if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    print('主进程号:', os.getpid())

"""
执行结果:
主进程号: 8248
子进程号: 7996
子进程的父进程号: 8248
"""

判断进程是否存活与杀死子进程

进程对象提供了查看进程是否存活的方法:is_alive()方法;还有杀死子进程的方法:terminate()方法

from multiprocessing import Process
import time

def task():
    print('子进程执行中')
    time.sleep(2)
    print('子进程结束')

if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    # 查看进程是否存活
    print(p.is_alive())
    # 先让子进程运行一段
    time.sleep(1)
    # 杀死子进程
    p.terminate()
    # 杀死子进程需要一点时间,所以短暂休眠一下
    time.sleep(0.1)
    # 再次查看进程是否存活
    print(p.is_alive())

"""
执行结果:
True
子进程执行中
False
"""

僵尸进程与孤儿进程

僵尸进程

所有的子进程在运行结束之后都会变成僵尸进程(死了但没死透),还保留着pid和一些运行过程的中的记录便于主进程查看(短时间保存),这些信息会被主进程回收(这时候才彻底死了)。

子进程会被主进程回收的情况:

  1. 主进程正常结束
  2. 调用join方法

image

孤儿进程

通俗易懂,子进程存活着,父进程意外死亡,这个时候子进程会被操作系统自动接管(相当于孤儿院)。

守护进程

守护进程即该进程的死活全部参考守护的对象,对象结束了,那么该进程也结束了,把进程对象中的daemon值变为True就说明该进程是守护进程。

举例

from multiprocessing import Process
import time


def task():
    print('子进程正在执行')
    time.sleep(2)
    print('子进程结束了')

if __name__ == '__main__':
    p = Process(target=task)
    # 必须写在start前面
    p.daemon = True  # 将子进程设置为守护进程:主进程结束 子进程立刻结束
    p.start()
    print('主进程正在执行')
    time.sleep(1)
    print('主进程结束了')

执行结果:

主进程正在执行
子进程正在执行
主进程结束了

把子进程设置为守护进程后,主进程结束了,那么子进程也立马结束。

互斥锁

引入:每逢节假日抢票,手机上明明显示还有余票,但是点击购买的时候却提示已经没有票了,之后回到查询页面发现确实显示没有票了,这是因为在你点击购买的时候票被别人买走了。

明明多人一起买票,却不会出现1张票可以被多人买到,这就是因为互斥锁的存在。

互斥锁:防止多个进程操作同一份数据的时候会造成数据的错乱的情况,原理就是将并发变成串行,牺牲了效率但是保证的数据的安全

比如下面这个买票系统是没有使用互斥锁的情况:

import json
from multiprocessing import Process
import time
import random


# 查票
def search(name):
    with open(r'ticket_data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    print(f'用户{name}查询当前余票:%s' % data.get('ticket_num'))


# 买票
def buy(name):
    # 买票是需要再次查票的,因为期间其他人可能已经把票买走了
    with open(r'ticket_data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    # 模拟购买的时间
    time.sleep(random.randint(1, 3))
    # 判断是否还有余票
    if data.get('ticket_num') > 0:
        data['ticket_num'] -= 1
        with open(r'ticket_data.json', 'w', encoding='utf8') as f:
            json.dump(data, f)
        print(f'用户{name}抢票成功')
    else:
        print(f'用户{name}抢票失败,没有余票了')


def run(name):
    search(name)
    buy(name)


if __name__ == '__main__':
    data = {'ticket_num': 1}
    with open(r'ticket_data.json', 'w', encoding='utf8') as f:
        json.dump(data, f)
    # 模拟多人同时抢票
    for i in range(1, 5):
        p = Process(target=run, args=('用户:%s' % i,))
        p.start()

执行结果:

用户用户:2查询当前余票:1
用户用户:3查询当前余票:1
用户用户:1查询当前余票:1
用户用户:4查询当前余票:1
用户用户:2抢票成功
用户用户:4抢票成功
用户用户:3抢票成功
用户用户:1抢票成功

明明只有一张票,但所有人都抢到票了,这就是因为多个进程同时操作同一份数据会出现的问题,这个问题可以添加互斥锁来解决。

添加互斥锁代码

from multiprocessing import Process, Lock
mutex = Lock()
mutex.acquire()  # 抢锁
mutex.release()  # 放锁

放入到模拟抢票的代码中

import json
from multiprocessing import Process, Lock
import time
import random


# 查票
def search(name):
    with open(r'ticket_data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    print(f'用户{name}查询当前余票:%s' % data.get('ticket_num'))


# 买票
def buy(name):
    # 买票是需要再次查票的,因为期间其他人可能已经把票买走了
    with open(r'ticket_data.json', 'r', encoding='utf8') as f:
        data = json.load(f)
    # 模拟购买的时间
    time.sleep(random.randint(1, 3))
    # 判断是否还有余票
    if data.get('ticket_num') > 0:
        data['ticket_num'] -= 1
        with open(r'ticket_data.json', 'w', encoding='utf8') as f:
            json.dump(data, f)
        print(f'用户{name}抢票成功')
    else:
        print(f'用户{name}抢票失败,没有余票了')

# 传入锁的对象
def run(name, mutex):
    search(name)
    mutex.acquire()  # 抢锁
    buy(name)
    mutex.release()  # 放锁


if __name__ == '__main__':
    data = {'ticket_num': 1}
    with open(r'ticket_data.json', 'w', encoding='utf8') as f:
        json.dump(data, f)
    # 创建互斥锁
    mutex = Lock()
    # 模拟多人同时抢票
    for i in range(1, 5):
        # 传入锁的对象
        p = Process(target=run, args=('用户:%s' % i, mutex))
        p.start()

执行结果:

用户用户:2查询当前余票:1
用户用户:4查询当前余票:1
用户用户:1查询当前余票:1
用户用户:3查询当前余票:1
用户用户:2抢票成功
用户用户:4抢票失败,没有余票了
用户用户:1抢票失败,没有余票了
用户用户:3抢票失败,没有余票了

互斥锁并不能轻易使用,容易造成死锁现象,互斥锁只在处理数据的部分加锁,不能什么地方都加,不然会严重影响程序的效率。

死锁现象:有两个互斥锁,释放锁的代码分别在另一个进程内,比如进程A的解锁需要进程B先解锁中,进程B的解锁需要进程A先解锁。

PS:锁有很多种,除了互斥锁,还有行锁、表锁、乐观锁、悲观锁等。

image

IPC机制(进程间通信)

IPC机制指的是两个进程之间进行数据通信的过程,可以是主进程与子进程数据交互,也可以是两个子进程数据交互,但本质都是一样的:不同内存空间中的进程数据交互。

我们可以用Queue队列模块实现IPC机制。

from multiprocessing import Process, Queue

def producer(q):
    for i in range(3):
        q.put(i)
        print('子进程producer向队列中添加值:', i)

def consumer(q):
    for i in range(3):
        print('子进程consumer从队列中取值:', q.get())

if __name__ == '__main__':
    q = Queue(1)
    # 创建往队列中添加值的进程,并把队列对象当参数传入
    p = Process(target=producer, args=(q,))
    # 创建从队列中取值的进程,并把队列对象当参数传入
    p1 = Process(target=consumer, args=(q,))
    p.start()
    p1.start()

执行结果:

子进程producer向队列中添加值: 0
子进程consumer从队列中取值: 0
子进程producer向队列中添加值: 1
子进程consumer从队列中取值: 1
子进程producer向队列中添加值: 2
子进程consumer从队列中取值: 2

生产者消费者模型

生产者即负责生产/制作数据,消费者即负责消费/处理数据。

比如刚刚用Queue队列模块实现IPC机制中,producer方法即是生产者(往队列里添加值),consumer方法即是消费者(从队列中取值)。

遇到该模型需要考虑的问题其实就是供需平衡的问题,生产力与消费力要均衡,比如下面的代码中就是消费力等于生产力。

from multiprocessing import Process, Queue
import time
import random

def producer(name, food, q):
    for i in range(3):
        data = f'{name}生产了第{i}个{food}'
        print(data)
        # 模拟生产过程
        time.sleep(random.randint(1, 3))
        q.put(food)

def consumer(name, q):
    # 写个死循环,确保队列数据全部取出
    while True:
        food = q.get()
        # 判断是否把队列数据消费完了
        if food is None: break
        time.sleep(random.random())
        print(f'{name}吃了一个{food}')

if __name__ == '__main__':
    q = Queue()
    # 生产者一
    p1 = Process(target=producer, args=('jason', '汉堡', q))
    # 生产者二
    p2 = Process(target=producer, args=('tom', '炸鸡', q))
    # 消费者一
    c1 = Process(target=consumer, args=('tony', q))
    # 消费者二
    c2 = Process(target=consumer, args=('david', q))
    p1.start()
    p2.start()
    c1.start()
    c2.start()
    p1.join()
    p2.join()
    # None用于判断是否生产结束,有两个消费者所以要添加两个值
    q.put(None)
    q.put(None)

拓展:使用None用于判断是否生产结束具有不确定性,我们不知道生产者有几个时就无法使用了,所以我们这里可以使用JoinableQueue()创建队列,并修改部分代码

from multiprocessing import Process, JoinableQueue
import time
import random

def producer(name, food, q):
    for i in range(3):
        data = f'{name}生产了第{i}个{food}'
        print(data)
        # 模拟生产过程
        time.sleep(random.randint(1, 3))
        q.put(food)

def consumer(name, q):
    # 写个死循环,确保队列数据全部取出
    while True:
        food = q.get()
        # 判断是否把队列数据消费完了
        if food is None: break
        time.sleep(random.random())
        print(f'{name}吃了一个{food}')
        q.task_done()  # 每次取数据都给队列反馈,让队列自己知道是否还有数据

if __name__ == '__main__':
    # 使用JoinableQueue()创建队列
    q = JoinableQueue()
    # 生产者一
    p1 = Process(target=producer, args=('jason', '汉堡', q))
    # 生产者二
    p2 = Process(target=producer, args=('tom', '炸鸡', q))
    # 消费者一
    c1 = Process(target=consumer, args=('tony', q))
    # 消费者二
    c2 = Process(target=consumer, args=('david', q))
    p1.start()
    p2.start()
    # 给消费者添加守护进程,如果q.join()执行完了,那么消费者也该结束了
    c1.daemon = True
    c2.daemon = True
    c1.start()
    c2.start()
    p1.join()
    p2.join()
    # 让队列全部数据取出才会往下执行
    q.join()

image

posted @ 2022-06-14 20:18  Yume_Minami  阅读(61)  评论(0编辑  收藏  举报