39---并发编程之进程2

一 进程对象及其他方法

一台计算机上面运行着很多进程,计算是如何区分并管理这些进程服务端的呢:
	计算机会给每一个运行的进程分配一个PID号
	Windows电脑进入cmd输入tasklist即可查看所有进程的进程号
		输入tasklist|findir pid号可查看对应的进程
	mac进入终端输入ps aux
  • 查看进程号
# 查看当前进程的进程号---方法一---current_process().pid
from multiprocessing import Process,current_process
import time
import os
def task():
    print(f'{current_process().pid} is run')
    time.sleep(15)

if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    print('主',current_process().pid)
    
# 查看当前进程的进程号---方法一二--getpid()
from multiprocessing import Process,current_process
import time
import os
def task():
    # 查看当前进程的进程号
    print(f'{os.getpid()} is run')
    # 查看当前进程的主进程号
    print(f'{os.getppid()}')
    time.sleep(15)

if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    print('主',os.getpid())
    # 当前主进程的主进程号
    print('猪猪',os.getppid())


  • 终止进程
from multiprocessing import Process,current_process
import time
import os
def task():
    # 查看当前进程的进程号
    print(f'{os.getpid()} is run')
    # 查看当前进程的主进程号
    print(f'{os.getppid()}')
    time.sleep(15)

if __name__ == '__main__':
    p = Process(target=task)
    p.start()
    p.terminate() # 杀死当前进程
    # 这句话是告诉操作系统帮你杀死当前进程
    # 需要一定时间,而代码的运行速度极快
    print(p.is_alive()) # 判断当前进程是否存活
    '''
    一般情况下我们会将存储布尔值的变量名和返回
    的结果是布尔值的方法名,都起成is_开头
    '''
    print('主',os.getpid())
    # 当前主进程的主进程号
    print('猪猪',os.getppid())

二 僵尸进程与孤儿进程(了解)

  • 僵尸进程
僵尸:死了但是没有死透
当你开设了子进程之后该进程死后不会立刻释放占用的进程号
因为我要让父进程能够查看到它开设的子进程的一些基本信息---占用的PID号,运行时间
所有的进程都会步入僵尸进程
	可能存在下属情况:父进程不死并且无限制的创建子进程并且子进程也不结束,就是有害的
	父进程回收子进程占用的PID号:
		1 父进程等待子进程运行结束
		2 父进程调用join方法
  • 孤儿进程
子进程存活,父进程意外死亡
操作系统会开设一个儿童福利院专门管理孤儿进程回收相关资源

三 守护进程

  • 介绍
你死我死,你活我活
  • 代码实现
from multiprocessing import Process
import time
def task(name):
    print(f'{name} 太监总管正在或者')
    time.sleep(3)
    print(f'{name}已经死亡')
    
if __name__ == '__main__':
    p = Process(target=task,kwargs={'name':'egon'})
    # 这句话一定要放在start上面,否则会报错
    p.daemon = True  # 将进程p设置成守护进程
    p.start()
    
    print('秦始皇寿终正寝!')

四 互斥锁

针对多个进程操作同一份数据的时候,会出现数据错乱的问题
针对上述问题解决方式就是加锁处理:
	****将并发变成串行,牺牲效率但是保证了数据的安全****
from multiprocessing import Process,Lock
import json
import time
import random

# 查票
def search(i):
    # 文件操作读取票数
    with open('data','r',encoding='utf-8') as f:
        dic = json.load(f)
    print(f'{i}查询余票{dic.get("ticket_num")}')

# 买票 先查再买
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'] -= 1
        # 写入数据库
        with open('data', 'w', encoding='utf-8') as f:
            json.dump(dic,f)
        print(f'{i}买票成功')
    else:
        print(f'{i}买票失败')

# 整合上面两个函数
def run(i,mutex):
    search(i)
    # 给买票环节加锁处理
    # 先抢锁
    mutex.acquire()
    buy(i)
    # 释放锁
    mutex.release()


if __name__ == '__main__':
    # 在主进程中生成一把锁,让所有的子进程抢,谁先抢到谁先买票
    mutex = Lock()
    for i in range(1,10):
        p = Process(target=run,args=(i,mutex))
        p.start()
        
"""
扩展:行锁 表锁
注意:
	1 锁不要轻易的使用,容易造成死锁现象(自己写代码一般不会用到,都是封装好的)
	2 锁只在处理数据的部分加来保证数据的安全(只在争抢数据的环节加锁处理即可)
"""
  • 使用面向对象整合上述代码
from multiprocessing import Process,Lock
import json
import random
import time
class People:
    def __init__(self,name):
        self.name = name

    def query(self):
        with open('data','r',encoding='utf-8') as f:
            dic = json.load(f)
            num = dic.get('ticket_num')
        print(f'{self.name} 查询到余票是{num}')
        return dic

    def buy(self):
        dic = self.query()
        time.sleep(random.randint(1,3))
        if dic.get('ticket_num') > 0:
            dic['ticket_num'] -= 1
            with open('data','w',encoding='utf-8') as f:
                json.dump(dic,f)
            print(f'{self.name}订票成功')
        else:
            print(f'{self.name}订票失败')

    def run(self,mutex):
        self.query()
        mutex.acquire()
        self.buy()
        mutex.release()

if __name__ == '__main__':
    mutex = Lock()
    for i in range(10):
        p = Process(target=People(i).run,args=(mutex,))
        p.start()

### 五 队列介绍

- 队列Queue模块

```markdown
队列:管道+锁
队列:先进先出
堆栈:先进后出
from multiprocessing import Queue

# 创建一个队列
q = Queue(5)  # 括号内可以传数字 标示生成的队列最大可以同时存放的数据量

q.put(111)
q.put(222)
q.put(333)
# print(q.full())  # 判断当前队列是否满了
# print(q.empty())  # 判断当前队列是否空了
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_nowait()  # 没有数据直接报错queue.Empty
# v6 = q.get(timeout=3)  # 没有数据之后原地等待三秒之后再报错  queue.Empty
try:
    v6 = q.get(timeout=3)
    print(v6)
except Exception as e:
    print('一滴都没有了!')

# # v6 = q.get()  # 队列中如果已经没有数据的话 get方法会原地阻塞
# print(v1, v2, v3, v4, v5, v6)

"""
q.full()
q.empty()
q.get_nowait()
在多进程的情况下是不精确
"""

六 进程间通信IPC机制

  • IPC机制

借助队列完成进程与进程之间的通信

from multiprocessing import Queue,Process

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

def run(q):
    q.put('25服务')
    print('hello py')
def consumer(q):
    print(q.get())
    
    
if __name__ == '__main__':
    q = Queue()
    p = Process(target=run,args=(q,))
    p1 = Process(target=consumer,args=(q,))
    p.start()
    p1.start()

七 生产者消费者模型

生产者:生产/制造数据
消费者:消费/处理数据
该模型除了上述两个之外还需要一个介质:
	生活中的例子:做包子的包子做好后放在蒸笼(介质)里面,买包子的从蒸笼中拿包子
	生产者和消费者不是直接做交互的,而是借助于媒介
生产者(做包子的)+消息队列(蒸笼)+消费者(吃包子的)
from multiprocessing import Process,Queue,JoinableQueue
import time
import random

def produser(name,food,q):
    for i in range(5):
        data = f'{name} 生产了 {food}{i}个'
        # 模拟延迟
        time.sleep(random.randint(1,3))
        print(data)
        # 将数据放入队列中
        q.put(data)
def consumer(name,q):
    # 消费者胃口很大,光盘行动,生产的事物全部吃掉
    while True:
        food = q.get() # 没有数据就会卡住
        # 判断当前是否有结束的表示
        # if food is None:
        #     break
        time.sleep(random.randint(1,3))
        print(f'{name} 吃了 {food}')
        q.task_done() # 告诉队列你已经从队列中取出一个数据并处理完毕了


if __name__ == '__main__':
    # 先做蒸笼
    # q = Queue()
    q = JoinableQueue()
    p1 = Process(target=produser,args=('egon','包子',q))
    p2 = Process(target=produser,args=('tank','煲汤',q))
    c1 = Process(target=consumer,args=('春哥',q))
    c2 = Process(target=consumer,args=('新哥',q))
    p1.start()
    p2.start()
    # 将消费者是设置成守护进程
    c1.daemon = True
    c2.daemon =True
    c1.start()
    c2.start()

    p1.join()
    p2.join()
    # 等待生产者生产完毕之后,往队列中添加特定的结束符号
    # 有几个消费者,所以需要几个None
    # q.put(None)  # None在生产者生产数据的末尾
    # q.put(None)  # None在生产者生产数据的末尾
    q.join() # 等待队列中所有数据被取完在执行往下的代码

'''
JoinableQueue每当往该队列中存入数据的时候,内部会有一个计数器+1
每当调用task_done的时候,计数器-1
q.join()当计数为0的时候,才往后运行
只要q.join()执行完毕,说明消费者已经处理完数据了,消费者就没有
存在的必要了---守护进程
'''

八 线程相关知识点

  • 什么是线程
进程:资源单位
线程:执行单位

将操作系统比喻成一个大的工厂
那么进程就相当于工厂中的车间
而线程就是车间里面的流水线

每一个进程肯定自带一个线程

再次总结:
	进程:资源单位,起一个进程仅仅只是在内存空间中开辟一块独立的内存车间
	线程:执行单位,真正被CPU执行的其实是进程中的线程,线程指的就是代码的执行过程,执行代码中所需使用到的资源都找所需要的进程索要
	
进程和线程都是虚拟单位,只是为了更加方便的描述问题
  • 为何要有线程
开设进程
	1 申请内存空间,耗资源
	2 ‘拷贝代码’   耗资源
开线程
	一个进程内可以开设多个线程,可以在一个进程内可以开设多个线程无需再次申请内存空间
	
总结:开设线程的开销要远远小于线程的开销
	 同一个进程下的多个线程数据是共享的

举例说明:
	开发一款文本编辑器
		获取用户输入
		实时输出到屏幕
		自动保存到硬盘
	针对上述三个功能,开始进程合适还是线程合适
		开三个线程处理上述三个功能更加合适
  • 如何使用

loading.....

posted @ 2020-04-23 14:22  微信搜索-程序媛小庄  阅读(258)  评论(0编辑  收藏  举报