【2.0】知识点小结(进程进阶)

【2.0】知识点小结(进程进阶)

【一】进程对象及其他方法

【1】查看当前进程的进程号

  • 一台计算机上面运行着很多进程,那么计算机是如何区分并管理这些进程服务端呢?
    • 计算机会给每一个运行的进程分配一个PID号
  • 如何查看?
    • Windows系统
      • CMD 命令行 tasklist 即可查看
    • Mac系统
      • 终端运行 ps aux 即可查看
  • 如何根据指定进程号查看进程
    • Mac系统
      • 终端运行 ps aux|grep PID 即可查看
    • Windows系统
      • CMD 命令行 tasklist |findstr PID 即可查看

(1)查看当前进程的进程号current_process().pid 方法

# -*-coding: Utf-8 -*-
# @File : 01 进程对象及其他方法 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24
from multiprocessing import Process, current_process
import os
import time


def task():
    # 查看当前进程的 进程(PID) 号
    print(f'当前程序:>>>>{current_process().pid} 正在运行')
    time.sleep(2)


if __name__ == '__main__':
    p = Process(target=task)
    p.start()

    print(f'这是主程序:>>>{current_process().pid}')

    # 这是主程序:>>>11168
    # 当前程序:>>>>10944 正在运行

(2)查看当前进程的进程号os.getpid() 方法

# -*-coding: Utf-8 -*-
# @File : 01 进程对象及其他方法 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24
from multiprocessing import Process, current_process
import os
import time


def task():
    # 查看当前进程的 进程(PID) 号
    print(f'当前程序:>>>>{os.getpid()} 正在运行')
    time.sleep(2)


if __name__ == '__main__':
    p = Process(target=task)
    p.start()

    print(f'这是主程序:>>>{os.getpid()}')

    # 这是主程序:>>>9928
    # 当前程序:>>>>3912 正在运行

(3)查看当前进程的父进程的进程号os.getpid() 方法

# -*-coding: Utf-8 -*-
# @File : 01 进程对象及其他方法 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24
from multiprocessing import Process, current_process
import os
import time


def task():
    # 查看当前进程的 进程(PID) 号
    print(f'当前程序:>>>>{os.getpid()} 正在运行')
    # 查看当前进程的 父进程(PID) 号
    print(f'当前程序的父进程:>>>>{os.getppid()} 正在运行')
    time.sleep(2)


if __name__ == '__main__':
    p = Process(target=task)
    p.start()

    print(f'这是主程序:>>>{os.getpid()}')
    print(f'这是主程序的父进程:>>>{os.getppid()}')

    # 这是主程序:>>>22236
    # 这是主程序的父进程:>>>17720
    # 当前程序:>>>>3756 正在运行
    # 当前程序的父进程:>>>>22236 正在运行

【2】杀死当前进程p.terminate()

告诉操作系统帮我去杀死当前进程

但是需要一定的时间。

代码的运行速度极快

【3】判断当前进程是否存活p.is_alive()

# -*-coding: Utf-8 -*-
# @File : 01 进程对象及其他方法 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24
from multiprocessing import Process, current_process
import os
import time


def task():
    # 查看当前进程的 进程(PID) 号
    print(f'当前程序:>>>>{os.getpid()} 正在运行')
    print(f'当前程序的父进程:>>>>{os.getppid()} 正在运行')

    time.sleep(2)


if __name__ == '__main__':
    p = Process(target=task)
    p.start()

    # 杀死当前进程 - 需要给操作系统一点缓冲时间
    p.terminate()
    time.sleep(0.2)
    # 判断当前进程是否存活
    print(p.is_alive())

    print(f'这是主程序:>>>{os.getpid()}')
    print(f'这是主程序的父进程:>>>{os.getppid()}')

    # 主进程正常
    # 调用程序中的子进程被杀死
    # False
    # 这是主程序:>>>16796
    # 这是主程序的父进程:>>>17720

一般默认会将

​ 存储布尔值的变量名

​ 返回的结果是布尔值的方法名

起成is_ 开头的变量名

【二】僵尸进程与孤儿进程

【1】僵尸进程

僵尸进程是指完成了自己的任务,但父进程没有正确地释放它所占用的系统资源

导致它仍然存在于进程列表中

但已经停止了运行。

这些僵尸进程会占据一定的系统内存,并在一定程度上影响系统的性能。

  • 当子进程开设后,该进程死后不会立刻释放占用的进程号
    • 因为要让父进程能够查看到开设的子进程的一些基本信息
      • 占用的 PID 号,运行时间等
  • 所有的进程都会步入僵尸进程
    • 父进程不死并且在无限制的创建子进程并且子进程也不结束
  • 如何回收子进程占用的 PID 号
    • 父进程等待子进程运行结束
    • 父进程调用 join 方法
# -*-coding: Utf-8 -*-
# @File : 02 僵尸进程 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24

from multiprocessing import Process
import time


def run():
    print(f'the run is beginning:>>>>')
    time.sleep(1)
    print(f'the run is ending:>>>>')


if __name__ == '__main__':
    p = Process(target=run)

    p.start()

    print(f'这是主程序的执行方法:>>>>')
    # 这是主程序的执行方法:>>>>
    # the run is beginning:>>>>
    # the run is ending:>>>>

【2】孤儿进程

孤儿进程则是指父进程在子进程终止之前就已经退出了

导致子进程失去了与父进程通信的能力。

这些孤儿进程将被init进程接管

init进程会等待它的状态信息并释放它的系统资源。

  • 子进程存活,父进程意外死亡
    • 没有父进程来帮助回收 PID 号
  • 解决办法
    • 操作系统会开设一个儿童福利院(init 进程)专门管理孤儿进程回收相关资源

【三】守护进程

守护进程 (daemon) 是在计算机系统启动时就已经运行,并且一直在后台运行的一类特殊进程。

它们通常不与用户直接交互,也不接受标准输入和输出,而是在后台执行某种任务或提供某种服务。

守护进程往往是由系统管理员手动启动的,它们可以在系统启动时自动启动,一直运行在后台,直到系统关闭或被停止。

常见的守护进程包括网络服务 (如 web 服务器、邮件服务器、 ftp 服务器等)、日志记录系统 (如系统日志服务、应用程序日志服务等) 等。

守护进程通常在后台运行,不需要用户交互,并且有较高的权限,因此编写守护进程需要特别注意安全性和稳定性。

【1】引入 - 主进程死亡,子进程未死亡

# -*-coding: Utf-8 -*-
# @File : 03 守护进程 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24

from multiprocessing import Process
import time


def task(name):
    print(f'总管:>>{name}>>正常存活')
    time.sleep(2)
    print(f'总管:>>{name}>>正常死亡')


if __name__ == '__main__':
    print(f'皇帝 :>>> ChiMeng >>> 执掌江山')
    p = Process(target=task, args=('dream',))
    # p = Process(target=task, kwargs={"name":"dream"})

    p.start()

    print(f'皇帝 :>>> ChiMeng >>> 寿终正寝')

    # 皇帝 :>>> ChiMeng >>> 执掌江山
    # 皇帝 :>>> ChiMeng >>> 寿终正寝
    # 总管:>>dream>>正常存活
    # 总管:>>dream>>正常死亡

【2】使用 - 主进程死亡,子进程必死亡

(1)错误使用演示

# -*-coding: Utf-8 -*-
# @File : 03 守护进程 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24

from multiprocessing import Process
import time


def task(name):
    print(f'总管:>>{name}>>正常存活')
    time.sleep(2)
    print(f'总管:>>{name}>>正常死亡')


if __name__ == '__main__':
    print(f'皇帝 :>>> ChiMeng >>> 执掌江山')
    p = Process(target=task, args=('dream',))
    # p = Process(target=task, kwargs={"name":"dream"})

    p.start()

    # 将进程 p 设置成守护进程
    p.daemon = True

    print(f'皇帝 :>>> ChiMeng >>> 寿终正寝')

    #     assert self._popen is None, 'process has already started'
    # AssertionError: process has already started
    # 报错原因:进程已经开始了无法再次重置进程状态

    # 总管:>>dream>>正常存活
    # 总管:>>dream>>正常死亡

(2)正确使用演示

将设置主进程的方法在 调用主进程 的前面声明

# -*-coding: Utf-8 -*-
# @File : 03 守护进程 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24

from multiprocessing import Process
import time


def task(name):
    print(f'总管:>>{name}>>正常存活')
    time.sleep(2)
    print(f'总管:>>{name}>>正常死亡')


if __name__ == '__main__':
    print(f'皇帝 :>>> ChiMeng >>> 执掌江山')
    p = Process(target=task, args=('dream',))
    # p = Process(target=task, kwargs={"name":"dream"})

    # 将进程 p 设置成守护进程
    p.daemon = True

    p.start()

    print(f'皇帝 :>>> ChiMeng >>> 寿终正寝')

    # 皇帝 :>>> ChiMeng >>> 执掌江山
    # 皇帝 :>>> ChiMeng >>> 寿终正寝

【四】互斥锁

互斥锁(Mutex)是一种用于多线程编程中控制对共享资源访问的机制。

其作用是保证在同一时刻只有一个线程在访问共享资源,从而避免多个线程同时读写数据造成的问题。

互斥锁的基本原理是在对共享资源进行访问前加锁,使得其他线程无法访问该资源,当访问完成后再解锁,使得其他线程可以进行访问。

通过这种方式,可以保证同一时间只有一个线程在执行关键代码段,从而保证了数据的安全性。

需要注意的是,互斥锁会带来一些额外的开销,

【1】数据错乱问题

多个进程操作同一份数据的时候,会出现数据错乱的问题

# -*-coding: Utf-8 -*-
# @File : 04 互斥锁 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24

from multiprocessing import Process
import json
import time
import random


# 拿到票的数据
def get_ticket():
    with open('ticket_data', 'r', encoding='utf8') as f:
        ticket_dict = json.load(f)
    return ticket_dict


# 存储票的数据
def save_ticket(ticket_dict):
    with open('ticket_data', 'w', encoding='utf8') as f:
        json.dump(ticket_dict, f)


# (1)查票
def search_ticket(name):
    # 模拟从数据库读取票数
    # 打开文件读取票数
    ticket_dict = get_ticket()
    print(f'用户:>>>{name} 正在查询余票:>>>{ticket_dict.get("ticket_number")}')


# (2)买票
def buy_ticket(name):
    # (2.1)先查票
    ticket_dict = get_ticket()

    # (2.2)模拟网络延迟
    time.sleep(random.randint(1, 3))

    # (2.3) 判断当前是否有票
    if ticket_dict.get('ticket_number') > 0:
        # (2.5)有票再买票
        ticket_dict['ticket_number'] -= 1
        save_ticket(ticket_dict)
        print(f'用户:>>>{name} 买票成功!!')
    else:
        # (2.4) 没票则反馈信息
        print(f'当前无余票!!')


# 整合成一个人的功能
def main(name):
    search_ticket(name)
    buy_ticket(name)


if __name__ == '__main__':
    # 多进程演示 - 多个人购票
    for i in range(1, 5):
        p = Process(target=main, args=(i,))
        p.start()

    # 所有人都能查票成功,且能购票成功
    # 原因是所有人都查票的时候,数据发生了错乱
    # 用户:>>>1 正在查询余票:>>>2
    # 用户:>>>2 正在查询余票:>>>2
    # 用户:>>>3 正在查询余票:>>>2
    # 用户:>>>4 正在查询余票:>>>2
    # 用户:>>>2 买票成功!!
    # 用户:>>>4 买票成功!!
    # 用户:>>>1 买票成功!!
    # 用户:>>>3 买票成功!!

【2】加锁处理

针对上述数据错乱问题,解决方式就是加锁处理

将并发变成串行牺牲效率,但是保证了数据的安全

# -*-coding: Utf-8 -*-
# @File : 04 互斥锁 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24

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


# 拿到票的数据
def get_ticket():
    with open('ticket_data', 'r', encoding='utf8') as f:
        ticket_dict = json.load(f)
    return ticket_dict


# 存储票的数据
def save_ticket(ticket_dict):
    with open('ticket_data', 'w', encoding='utf8') as f:
        json.dump(ticket_dict, f)


# (1)查票
def search_ticket(name):
    # 模拟从数据库读取票数
    # 打开文件读取票数
    ticket_dict = get_ticket()
    print(f'用户:>>>{name} 正在查询余票:>>>{ticket_dict.get("ticket_number")}')


# (2)买票
def buy_ticket(name):
    # (2.1)先查票
    ticket_dict = get_ticket()

    # (2.2)模拟网络延迟
    time.sleep(random.randint(1, 3))

    # (2.3) 判断当前是否有票
    if ticket_dict.get('ticket_number') > 0:
        # (2.5)有票再买票
        ticket_dict['ticket_number'] -= 1
        save_ticket(ticket_dict)
        print(f'用户:>>>{name} 买票成功!!')
    else:
        # (2.4) 没票则反馈信息
        print(f'用户:>>>{name} 当前无余票!!')


# 整合成一个人的功能
def main(name, mutex):
    # 所有人都去查票
    search_ticket(name)

    # 给买票环节加锁处理
    # (1)先强锁
    mutex.acquire()
    buy_ticket(name)
    # (2)抢完后释放锁
    mutex.release()


if __name__ == '__main__':
    # 在主进程中生成一把锁,让所有的子进程去抢,谁抢到谁就先买票
    mutex = Lock()

    # 多进程演示 - 多个人购票
    for i in range(1, 5):
        p = Process(target=main, args=(i, mutex))
        p.start()

    # 所有人都能查票成功,但是只有前两名购票成功
    # 前两名是随机抢到的,不一定是按顺序(考虑到各种因素,如网络等)
    # 用户:>>>1 正在查询余票:>>>2
    # 用户:>>>2 正在查询余票:>>>2
    # 用户:>>>3 正在查询余票:>>>2
    # 用户:>>>4 正在查询余票:>>>2
    # 用户:>>>1 买票成功!!
    # 用户:>>>2 买票成功!!
    # 用户:>>>3 当前无余票!!
    # 用户:>>>4 当前无余票!!

【3】行锁与表锁

行锁和表锁是数据库中常用的锁定机制。

(1)行锁

  • 行锁是在对数据库表中的某个数据行进行修改时
    • 一次只允许一个用户操作该行
    • 其他用户如果需要修改该行数据就必须等待。
  • 通过行锁定可以避免多个用户同时修改同一行数据所导致的数据不一致问题。

(2)表锁

  • 表锁则是当一个用户操作某个数据库表时
    • 会锁定整个表,其他用户同时不能操作该表。
  • 这在一些特殊场景下比如表维护、备份等是非常有用的。

(3)小结

  • 总的来说
    • 行锁定是比较细粒度的锁定
    • 而表锁定则是更为粗粒度的锁定方法。

【4】注意

  1. 锁不要轻易使用,容易造成死锁现象
  2. 锁只在处理数据的部分加,用来保证数据的安全(只在争抢数据的环节加锁

【五】进程间通信

借助于:消息队列

管道:subprocess

​ stdin stdout stderr

不同类型的信息分别存储在不同的管道内

当我们进行read方法后,其他进程无法再拿到指定管道内的其他消息

队列: 管道 + 锁

队列:先进先出

堆栈:后进后出

存取数据:存是为了更好的取

​ 千方百计的存

​ 简单快捷的取

【1】队列模块:Queue

(1)简单使用

# -*-coding: Utf-8 -*-
# @File : 05 队列模块 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24
import queue
from multiprocessing import Queue

# 创建队列
# 括号内可以传参数,表示生成的队列最大可以同时存放的数据量
# 默认 为 0
q = queue.Queue(5)

# 向队列中放数据
q.put(111)

# 在队列中取数据
res_get = q.get()
print(res_get)
# 111

当队列数据放满了以后,如果还有数据要放程序就会阻塞,直到有位置让出来(不会报错)

队列中如果已经没有数据的话,程序也会阻塞

(2)没有数据抛出异常.get_nowait()

队列中没有数据直接抛出异常

# -*-coding: Utf-8 -*-
# @File : 05 队列模块 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24
import queue
from multiprocessing import Queue

# 创建队列
# 括号内可以传参数,表示生成的队列最大可以同时存放的数据量
# 默认 为 0
q = queue.Queue(5)

# 向队列中放数据
q.put(111)
q.put(222)

# 在队列中取数据
res_get = q.get()
res_get1 = q.get()
res_get2 = q.get_nowait()

print(res_get)

# raise Empty
# _queue.Empty

(3)没有数据延迟抛出异常.get(timeout=3)

队列中没有数据,延迟三秒抛出异常

# -*-coding: Utf-8 -*-
# @File : 05 队列模块 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24
import queue
from multiprocessing import Queue

# 创建队列
# 括号内可以传参数,表示生成的队列最大可以同时存放的数据量
# 默认 为 0
q = queue.Queue(5)

# 向队列中放数据
q.put(111)
q.put(222)

# 在队列中取数据
res_get = q.get()
res_get1 = q.get()
res_get2 = q.get(timeout=3)

print(res_get)
# raise Empty
# _queue.Empty

(4)判断当前队列数据是否填满队列.full()

# -*-coding: Utf-8 -*-
# @File : 05 队列模块 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24
import queue
from multiprocessing import Queue

# 创建队列
# 括号内可以传参数,表示生成的队列最大可以同时存放的数据量
# 默认 为 0
q = queue.Queue(3)

# 向队列中放数据
q.put(111)
print(q.full())
# False
q.put(222)
print(q.full())
# False
q.put(333)
print(q.full())
# True


# 在队列中取数据
res_get = q.get()
res_get1 = q.get()

(5)判断当前队列是否为空.empty()

# -*-coding: Utf-8 -*-
# @File : 05 队列模块 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24
import queue
from multiprocessing import Queue

# 创建队列
# 括号内可以传参数,表示生成的队列最大可以同时存放的数据量
# 默认 为 0
q = queue.Queue(3)

# 向队列中放数据
q.put(111)

# 判断当前队列数据是否填满队列
print(q.full())
# False
q.put(222)
print(q.full())
# False
q.put(333)

# 判断当前队列数据是否填满队列
print(q.full())
# True


# 在队列中取数据
res_get = q.get()
res_get1 = q.get()

# 判断当前队列是够为空
print(q.empty())
# False

res_get3 = q.get()
# 判断当前队列是够为空
print(q.empty())
# True

(6)小结

# 判断当前队列是否满了
q.full()

# 判断当前队列是够空了
q.empty()

# 判断当前队列是否为空
# 如果队列为空,该方法将引发 queue.Empty 异常
q.get_nowait()

以上方法在多进程 的情况下不精确

​ 多进程的队列切换时间是非常短的,存在可能判断失误

【2】IPC机制

IPC机制指进程间通信机制(Inter-Process Communication),它是指在不同进程间传输数据或者信息的一种机制。

在多进程操作系统中,各个进程相互独立,不能直接访问对方的内存空间,所以必须通过特殊的通信方式实现进程之间的信息交换和协调。

常见的IPC机制包括管道、消息队列、共享内存和信号量等方式。

​ 其中,管道、消息队列和共享内存都是用于进程之间的数据传输

​ 而信号量则是用于进程之间的同步与互斥。

通过使用IPC机制,可以使得不同进程能够有效地协同工作,在复杂的程序设计中起到十分重要的作用。

(1)子进程与主进程之间通过队列进行通信

# -*-coding: Utf-8 -*-
# @File : 06 IPC机制 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24
from multiprocessing import Process, Queue


# 需求
# (1)主进程与字进程之间借助于队列进行通信
# (2)子进程与子进程之间借助于队列进行通信


def producer(que_ue):
    que_ue.put('this is a put message')
    print('this in a queue')


if __name__ == '__main__':
    que_ue = Queue()
    pro_cess = Process(target=producer, args=(que_ue,))

    pro_cess.start()
    msg_from_p = que_ue.get()

    print(msg_from_p)
    # this in a queue
    # this is a put message

(2)子进程与子进程之间借助队列进行通信

# -*-coding: Utf-8 -*-
# @File : 06 IPC机制 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24
from multiprocessing import Process, Queue


# 需求
# (1)主进程与字进程之间借助于队列进行通信
# (2)子进程与子进程之间借助于队列进行通信


def producer(que_ue):
    que_ue.put('this is a put message')
    print('this in a queue')


def customer(que_ue):
    msg_from_producer = que_ue.get()
    print(msg_from_producer)
    # this in a queue
    # this is a put message


if __name__ == '__main__':
    que_ue = Queue()
    p_producer = Process(target=producer, args=(que_ue,))
    p_customer = Process(target=customer, args=(que_ue,))

    p_producer.start()
    p_customer.start()

【六】生产者与消费者模型

生产者模型和消费者模型是指通过利用队列解耦生产者和消费者的一种并发编程模型。

在生产者模型中,生产者负责将数据放入共享队列中,而消费者则从队列中取出数据进行处理。

生产者和消费者之间通过共享这个队列来进行信息的交流。

这种模型适用于生产者和消费者之间的处理速度不一致的情况,同时还能够保证数据传输的安全性和正确性。

在消费者模型中,消费者负责向队列中插入任务,而由线程池中的工作线程进行任务的处理。

消费者和工作线程之间通过共享线程池中的任务队列来完成任务分发和执行。

这种模型适用于任务处理需要一定时间的情况,能够充分利用多线程的优势提高系统的并发性能,提高效率。

生产者:生产/制造东西

消费者:消费/处理东西

该模型还需要一个媒介

  • 比如做包子是先将包子做好后放在蒸笼(媒介)里面,买包子的去蒸笼里面拿

  • 厨师做菜之后用盘子(媒介)装着给消费者端过去

  • 生产者与消费者之间不是直接做交互的,而是借助于媒介

  • 生产者(做包子的) + 媒介(蒸包子) + 消费者(吃包子的)

【1】版本1.0

# -*-coding: Utf-8 -*-
# @File : 07 生产者模型与消费者模型 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24

from multiprocessing import Process, Queue
import time
import random


# 需求
# (1)主进程与字进程之间借助于队列进行通信
# (2)子进程与子进程之间借助于队列进行通信


def producer(name, food, q):
    '''

    :param name: 生产者名字
    :param food: 生产的数据:食物
    :param q: 队列
    :return:
    '''
    for i in range(1, 4):
        data = f'当前大厨:>>> [{name}] 生产出:>>>[{i}道{food}]菜'
        # 模拟延迟
        time.sleep(random.randint(1, 3))
        q.put(data)


def customer(name, q):
    '''

    :param name: 消费者名字
    :param q: 队列
    :return:
    '''
    # 假定消费者消费能力很强
    while True:
        food = q.get()
        # 模拟延迟
        time.sleep(random.randint(1, 3))
        print(f'当前消费者:>>>[{name}] 消费了:>>>[{food}]')


def main():
    pass


if __name__ == '__main__':
    # (1)建立模型(媒介,蒸笼)
    que_ue = Queue()
    # (2) 建立模型(生产者,厨师)
    p_producer_dream = Process(target=producer, args=('dream', '鱼香肉丝', que_ue))
    p_producer_sprout = Process(target=producer, args=('sprout', '宫保鸡丁', que_ue))
    # (3) 建立模型(消费者,顾客)
    p_customer_smile = Process(target=customer, args=('smile', que_ue))
    p_customer_sad = Process(target=customer, args=('sad', que_ue))

    p_producer_dream.start()
    p_producer_sprout.start()
    p_customer_smile.start()
    p_customer_sad.start()

    '''
    当前消费者:>>>[smile] 消费了:>>>[当前大厨:>>> [dream] 生产出:>>>[1道鱼香肉丝]菜]
    当前消费者:>>>[sad] 消费了:>>>[当前大厨:>>> [dream] 生产出:>>>[2道鱼香肉丝]菜]
    当前消费者:>>>[smile] 消费了:>>>[当前大厨:>>> [sprout] 生产出:>>>[1道宫保鸡丁]菜]
    当前消费者:>>>[smile] 消费了:>>>[当前大厨:>>> [sprout] 生产出:>>>[2道宫保鸡丁]菜]
    当前消费者:>>>[smile] 消费了:>>>[当前大厨:>>> [sprout] 生产出:>>>[3道宫保鸡丁]菜]
    当前消费者:>>>[sad] 消费了:>>>[当前大厨:>>> [dream] 生产出:>>>[3道鱼香肉丝]菜]
    '''

问题:消费者没有数据被卡主

【2】版本2.0

向队列的结尾添加结束标志的符号,作为结束的标志

# -*-coding: Utf-8 -*-
# @File : 07 生产者模型与消费者模型 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24

from multiprocessing import Process, Queue,JoinableQueue
import time
import random


# 需求
# (1)主进程与字进程之间借助于队列进行通信
# (2)子进程与子进程之间借助于队列进行通信


def producer(name, food, q):
    '''

    :param name: 生产者名字
    :param food: 生产的数据:食物
    :param q: 队列
    :return:
    '''
    for i in range(1, 4):
        data = f'当前大厨:>>> [{name}] 生产出的:>>>[{i}道{food}]菜'
        # 模拟延迟
        time.sleep(random.randint(1, 3))
        q.put(data)


def customer(name, q):
    '''

    :param name: 消费者名字
    :param q: 队列
    :return:
    '''
    # 假定消费者消费能力很强
    while True:
        food = q.get()

        # 对收到的信息进行判断:是否有结束的标志
        if food is None:
            print('消费者消费完毕:>>>!!')
            break

        # 模拟延迟
        time.sleep(random.randint(1, 3))
        print(f'当前消费者:>>>[{name}] 消费了:>>>[{food}]')


def main():
    pass


if __name__ == '__main__':
    # (1)建立模型(媒介,蒸笼)
    que_ue = Queue()
    # (2) 建立模型(生产者,厨师)
    p_producer_dream = Process(target=producer, args=('dream', '鱼香肉丝', que_ue))
    p_producer_sprout = Process(target=producer, args=('sprout', '宫保鸡丁', que_ue))
    # (3) 建立模型(消费者,顾客)
    p_customer_smile = Process(target=customer, args=('smile', que_ue))
    p_customer_sad = Process(target=customer, args=('sad', que_ue))

    p_producer_dream.start()
    p_producer_sprout.start()
    p_customer_smile.start()
    p_customer_sad.start()

    p_producer_dream.join()
    p_producer_sprout.join()
    # 等待生产者消费完毕之后,向队列中添加特定的结束符号
    que_ue.put(None)  # 本条数据一定是生产者将数据全部生产完之后添加进去的
    que_ue.put(None)  # 本条数据一定是生产者将数据全部生产完之后添加进去的
    
    '''
    当前消费者:>>>[smile] 消费了:>>>[当前大厨:>>> [dream] 生产出的:>>>[1道鱼香肉丝]菜]
    当前消费者:>>>[sad] 消费了:>>>[当前大厨:>>> [sprout] 生产出的:>>>[1道宫保鸡丁]菜]
    当前消费者:>>>[sad] 消费了:>>>[当前大厨:>>> [dream] 生产出的:>>>[2道鱼香肉丝]菜]
    当前消费者:>>>[smile] 消费了:>>>[当前大厨:>>> [sprout] 生产出的:>>>[2道宫保鸡丁]菜]
    当前消费者:>>>[sad] 消费了:>>>[当前大厨:>>> [sprout] 生产出的:>>>[3道宫保鸡丁]菜]
    消费者消费完毕:>>>!!
    当前消费者:>>>[smile] 消费了:>>>[当前大厨:>>> [dream] 生产出的:>>>[3道鱼香肉丝]菜]
    消费者消费完毕:>>>!!
    '''

问题:消费者的个数决定了添加标志的个数

【3】版本3.0 JoinableQueue模块

每当向队列对象中存入数据的时候,队列对象内部会有一个计数器 +1

每当调用一次 task_done() 方法的时候,队列对象内部的计数器 -1

join() 当计数器为 0 时,继续执行代码

# -*-coding: Utf-8 -*-
# @File : 07 生产者模型与消费者模型 .py
# author: Chimengmeng
# blog_url : https://www.cnblogs.com/dream-ze/
# Time:2023/6/24

from multiprocessing import Process, Queue, JoinableQueue
import time
import random


# 需求
# (1)主进程与字进程之间借助于队列进行通信
# (2)子进程与子进程之间借助于队列进行通信


def producer(name, food, q):
    '''

    :param name: 生产者名字
    :param food: 生产的数据:食物
    :param q: 队列
    :return:
    '''
    for i in range(1, 4):
        data = f'当前大厨:>>> [{name}] 生产出的:>>>[{i}道{food}]菜'
        # 模拟延迟
        time.sleep(random.randint(1, 3))
        q.put(data)


def customer(name, q):
    '''

    :param name: 消费者名字
    :param q: 队列
    :return:
    '''
    # 假定消费者消费能力很强
    while True:
        food = q.get()

        # 模拟延迟
        time.sleep(random.randint(1, 3))
        print(f'当前消费者:>>>[{name}] 消费了:>>>[{food}]')

        # 告诉队列已经从队列中已经取出一个数据了
        q.task_done()


def main():
    pass


if __name__ == '__main__':
    # (1)建立模型(媒介,蒸笼)
    que_ue = JoinableQueue()
    # (2) 建立模型(生产者,厨师)
    p_producer_dream = Process(target=producer, args=('dream', '鱼香肉丝', que_ue))
    p_producer_sprout = Process(target=producer, args=('sprout', '宫保鸡丁', que_ue))
    # (3) 建立模型(消费者,顾客)
    p_customer_smile = Process(target=customer, args=('smile', que_ue))
    p_customer_sad = Process(target=customer, args=('sad', que_ue))

    p_producer_dream.start()
    p_producer_sprout.start()

    # 将消费者设置成守护进程:主进程死亡,子进程跟着死亡
    p_customer_smile.daemon = True
    p_customer_sad.daemon = True

    p_customer_smile.start()
    p_customer_sad.start()

    p_producer_dream.join()
    p_producer_sprout.join()

    # 等待消息队列中的所有数据被取完再往下执行代码
    que_ue.join()
    # join() 当计数器为 0 时,消费者就没有存在的必要了
    
    '''
    当前消费者:>>>[smile] 消费了:>>>[当前大厨:>>> [dream] 生产出的:>>>[1道鱼香肉丝]菜]
    当前消费者:>>>[sad] 消费了:>>>[当前大厨:>>> [sprout] 生产出的:>>>[1道宫保鸡丁]菜]
    当前消费者:>>>[sad] 消费了:>>>[当前大厨:>>> [dream] 生产出的:>>>[2道鱼香肉丝]菜]
    当前消费者:>>>[smile] 消费了:>>>[当前大厨:>>> [sprout] 生产出的:>>>[2道宫保鸡丁]菜]
    当前消费者:>>>[sad] 消费了:>>>[当前大厨:>>> [sprout] 生产出的:>>>[3道宫保鸡丁]菜]
    消费者消费完毕:>>>!!
    当前消费者:>>>[smile] 消费了:>>>[当前大厨:>>> [dream] 生产出的:>>>[3道鱼香肉丝]菜]
    消费者消费完毕:>>>!!
    '''
posted @ 2023-06-25 08:33  Chimengmeng  阅读(11)  评论(0编辑  收藏  举报