并发02--进程02:进程对象、僵尸进程、守护进程、队列

1.进程对象及其他方法

pycharm激活码网站:http://idea.medeming.com/jets

0.系统查看进程id

# 计算机管理进程
  一台计算机上面运行着很多进程,那么计算机是如何区分并管理这些进程服务端的呢?
    :计算机会给每个运行的进程分配一个PID号
    
# 如何查看
Windows电脑	
    cmd-->tasklist
    tasklist |findstr PID	查看具体某PID的进程
        
Mac电脑		
    终端--ps aux
    ps aux|grep PID		查看具体某PID的进程


# 进程其他方法:
import os
from multiprocessing import Process, current_process

current_process().pid  # 查看当前进程的进程号

os.getpid()  # 查看当前进程的进程号
os.getppid()  # 查看当前进程的父进程号	

p = Process(target=task)

p.terminate()  # 终止当前 p进程
p.is_alive()  # 判断当前 p进程是否存活

# 是让操作系统去杀死当前进程,但需要执行时间,而代码的运行速度极快,所以需要稍加一点点时间
time.sleep(0.1)
print(p.is_alive())


# 例:
from multiprocessing import Process, current_process
import time
import os


def task():
    # 查看当前进程的进程号
    # print('子进程号是:{} is running'.format(current_process().pid))
    print('子进程号是:{} is running'.format(os.getpid()))

    # 查看当前进程的父进程号
    print('该子进程的父进程号是:{} '.format(os.getppid()))

    time.sleep(3)


if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    print('主进程号是:', current_process().pid)
    print('主进程的父进程号是(当前pycharm进程号):', os.getppid())

1.僵尸进程与孤儿进程(了解)

# 僵尸进程
    死了但是没有死透
当你开设了子进程之后,子进程终止后不会立即释放占用的进程号,会保留一些子进程的状态信息
如占用的PID号,运行时间等 (因为要使得其父进程能够查看到其子进程的一些基本信息)
如果保留的信息不释放,其进程号就会一直被占用,这种进程称之为僵尸进程。

所有进程都会步入僵尸进程状态
    特殊情况下:父进程不终止且无限制的创建子进程,并且子进程也不结束。此情况下,僵尸进程是有害的。
	
# 解决僵尸进程的方法:
    父进程回收子进程占用的状态信息:
    1.父进程等待子进程运行结束后,再结束父进程。
      因为其父进程结束了,不会存在还需要访问其子进程的状态信息了,子进程占用的信息就会自动释放
        
    2.父进程调用join方法,等待子进程结束,再继续执行后面代码。


# 孤儿进程
子进程存活,父进程意外死亡
操作系统会开设一个"儿童福利院" 专门管理孤儿进程,回收相关资源。

2.守护进程

# 守护进程会随着主进程的结束而结束。
其一:守护进程会在主进程代码执行结束后就终止
其二:守护进程内无法再开启子进程,否则抛出异常

from multiprocessing import Process
import time


def task(name):
    print('{} 总管正在活着'.format(name))
    time.sleep(3)
    print('{} 总管正在死亡'.format(name))


if __name__ == '__main__':
    p = Process(target=task, args=('egon',))
    # p = Process(target=task, kwargs={'name': 'egon'})  也可以以字典传参的形式

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

    p.start()
    print("皇帝寿终正寝")

3.互斥锁(了解)

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

针对上诉问题,解决方式就是加锁处理:将并发变成串行,牺牲效率,但是保证了数据的安全性

# 互斥锁:
就是将每次只能一个去操作数据的步骤,放进保险柜,锁起来。谁拿到钥匙,谁就可以来操作数据。


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


# 查票,以jason 文件模拟数据库
def search(i):
    # 文件操作读取票数
    with open('data', 'r', encoding='utf-8') as f:
        dic = json.load(f)
    print("用户{}查询余票{}".format(i, dic.get('ticket_num')))
    # 字典取值不要用[]的形式 推荐使用get,你写的代码打死都不能报错!!!


# 买票:1.先查 2.再买
def buy(i):
    # 先查票
    with open('data', 'r', encoding='utf-8') as f:
        dic = json.load(f)
    # 模拟网络延迟
    time.sleep(random.randint(1, 3))
    # 判断当前是否有票
    if dic.get('ticket_num') > 0:
        # 修改数据库 买票
        dic['ticket_num'] = 0
        with open('data', 'w', encoding='utf-8') as f:
            json.dump(dic, f)
        print("用户{}买票成功".format(i))
    else:
        print("用户{}买票失败".format(i))


# 整合上面两个函数
def run(i, mutex):
    search(i)

    # 给买票环节加锁处理 (对于那些需要每次只允许一个操作数据,将其放在 acquire 与 release 之间)
    # 取得锁
    mutex.acquire()

    buy(i)

    # 释放锁
    mutex.release()


if __name__ == '__main__':
    # 在主进程中生成一把锁,让所有的子进程抢,谁先抢到谁先买票
    mutex = Lock()
    for i in range(1, 11):
        p = Process(target=run, args=(i, mutex))
        p.start()

拓展:行锁、表锁

注意:
    1.锁不要轻易的使用,容易造成死锁现象(我们写代码一般不会用到,都是内部封装好的)
    2.锁只在处理数据的部分加(放在 .acquire() 与 .release() 之间),来保证数据安全

4.进程间通信

IPC(Inter-Process Communication)

IPC机制:实现进程之间通讯

管道:pipe 基于共享的内存空间

队列:pipe+锁的概念--->Queue
# 本地测试时,才可能会用到Queue,实际生产都是用别人封装好的功能非常强大,例如Redis,kafka,RQ

5.队列 queue

"""
队列:先进先出
堆栈:先进后出
"""


"""存取数据,存是为了更好的取。核心:千方百计的存,简单快捷的取"""

import queue

# 生成一个队列实例
q = queue.Queue(maxsize=5)  # 括号内可以传数字,标识生成的队列最大可以同时存放的数据量

# 往队列中存数据
q.put(111)
q.put(222)
q.put(333)

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


q.put(444)
q.put(555)

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

# q.put(666)  # 当队列数据放满了之后,如果还有数据要放,程序会阻塞,直到有位置让出来,再继续放入。不会报错

# 去队列取数据
v1 = q.get()  # 不写在一行,是为了代码的可读性
v2 = q.get()
v3 = q.get()
v4 = q.get()
v5 = q.get()

# print(q.empty())  # 判断队列是否空了

# v6 = q.get()   # 队列中如果已经没有数据的话,get方法会原地阻塞,直到队列有数据,再继续取出。不会报错

# v7 = q.get(timeout=3)  # 没有数据,等待3秒,再报错:queue.Empty 队列为空

# v8 = q.get_nowait()  # 没有数据直接报错:queue.Empty 队列为空

# q.join()  # 等待队列中所有数据被处理完,再往下执行代码
# (实际上意味着等到队列为空,再执行别的操作)

print(v1, v2, v3, v4, v5)

"""
总结: q.full()  q.empty()  q.get_nowait() 在多进程时不精确的。(因为你刚判断,另一个进程可能就操作了)    
"""

6.IPC机制

# IPC机制:进程间通信

from multiprocessing import Queue, Process

"""
研究思路:
    1.主进程跟子进程借助于队列通信
    2.子进程跟子进程借助于队列通信
"""


# 例1
def producer(q):
    q.put('我是23号技师,很高兴为你服务')
    print('hello big baby')


if __name__ == '__main__':
    q = Queue()
    p = Process(target=producer, args=(q,))
    p.start()
    print(q.get())  # 主进程中,打印了子进程的信息...


# 例2
def producer(q):
    q.put('我是23号技师,很高兴为你服务')


def consumer(q):
    print(q.get())  # 子进程中,打印了另一个子进程的信息...


if __name__ == '__main__':
    q = Queue()
    p1 = Process(target=producer, args=(q,))
    p2 = Process(target=consumer, args=(q,))
    p1.start()
    p2.start()

7.生产者消费者模型

生产者:生产或者制造东西
消费者:消费或者处理东西
该模型除了上述两个之外,还需要一个媒介来存储东西(是为了解决供需不平衡的问题)
	例:厨师做菜,食客吃菜,盘子为媒介
	生产者与消费者之间不是直接做交互的,而是借助于媒介做交互
	
生产者(厨师)+消息队列(盘子)+消费者(食客)


# 版本三:(最优版)
# 优化:
# 1.通过利用 JoinableQueue 类 调用 join() 和 task_done() 方法 来自动判断 队列中是否还有数据

# JoinableQueue 这个类内部,每当你往队列中存入数据时,内部有一个计数器 +1
# q.task_done() 调用时(常用在 get 取数据之后),计数器 -1
# q.join()  调用时,会等到计数器为0时,才往后运行。

# 2.将消费者进程设置成守护进程,等到主进程中执行完毕后,直接终止消费者进程。
# (因为主进程最后调用q.join,说明队列中没有数据了,且主进程后续没有执行代码,主进程结束),


from multiprocessing import JoinableQueue, Process
import time
import random


def producer(name, food, q):
    for i in range(1, 4):
        # 模拟生产
        data = '{}做了{}{}'.format(name, food, i)
        # 模拟延迟,生产时间
        time.sleep(3)
        print(data)
        # 模拟装菜,将数据放进消息队列中
        q.put(data)


def consumer(name, q):
    # 一直消费,光盘行动
    while True:
        # 模拟消费
        food = q.get()
        time.sleep(random.randint(1, 3))
        print('{}吃了{}'.format(name, food))
        q.task_done()  # 告诉当前队列 前一步的操作处理完毕(这里是取出了一个数据,且处理完毕)


if __name__ == '__main__':
    q = JoinableQueue()
    # # 设置生产者子进程,并开启
    p1 = Process(target=producer, args=('大厨egon', '包子', q))
    p2 = Process(target=producer, args=('马叉虫tank', '白菜', q))
    p1.start()
    p2.start()

    # 设置消费者子进程
    c1 = Process(target=consumer, args=('春哥', q))
    c2 = Process(target=consumer, args=('熊哥', q))

    # 并设置其为守护进程,开启
    c1.daemon = True
    c1.start()
    c2.daemon = True
    c2.start()

    # 主进程等待生产者子进程,全部生产完
    p1.join()
    p2.join()

    # 等待队列中所有数据被处理完,再往下执行代码
    q.join()

    # 只要q.join()执行完毕后,说明消费者已经处理完数据了,那么消费者的进程就没有必要存在了。
    # 由此可以将 消费者的进程 设置成守护进程
posted @ 2021-05-26 17:24  Edmond辉仔  阅读(16)  评论(0编辑  收藏  举报