day34.txt

上节课补充:

join方法 是让主进程等待子进程代码运行结束后在继续运行,不影响其他子进程的执行
进程对象以及其他方法:
一台计算机上面运行着很多进程,那么计算机是如何区分并管理的呢?
计算机会给每一个运行的进程分配一个PID号,在windows下在命令行输入tasklist查看PID,通过task|findstr PID查看具体的进程

当然也可以通过current_process或者os模块
current_process().pid 查看当前进程的进程号
os.getpid() 查看当前进程进程号
os.getppid() 查看当前进程的父进程号
进程名.terminate()杀死当前进程
进程名.is_alive() 判断进程是否存活
僵尸进程与孤儿进程:

僵尸进程:死了但是没有死透
当你开设子进程之后,该进程死后不会立刻释放被占用的进程号,以为我们要让父进程能够查看到子进程的一些信息 占用的pid号 运行时间等
所有的进程都会步入僵尸进程,当父进程没有结束并且在无限创造子进程时就会发生严重的占用事件,所以应该采取相应的措施,通过结束父进
程或者采用wait方法

孤儿进程:子程序存活,父进程意外死亡,没有父进程可以对子进程进行管理和控制,操作系统会开设一个类似于“儿童福利院”专门管理孤儿进程回收相关资源


守护进程:会随着主进程的结束而结束
主进程创建守护进程

其一:守护进程会在主进程代码执行结束后就终止

   其二:守护进程内无法再开启子进程,否则抛出异常

进程名.daemon = True 需要在创建子进程之前声明

互斥锁:多个进程同时操作同一份数据的时候,会出现数据错乱的问题,那么针对这个问题解决的方式就是上锁lock
(将并发变成串行,牺牲了效率但是保证了数据的安全)

from multiprocessing import Process,Lock
				import time,json,random
				def search():
				    dic=json.load(open('db'))
				    print('33[43m剩余票数%s33[0m' %dic['count'])

				def get():
				    dic=json.load(open('db'))
				    time.sleep(random.random()) #模拟读数据的网络延迟
				    if dic['count'] >0:
				        dic['count']-=1
				        time.sleep(random.random()) #模拟写数据的网络延迟
				        json.dump(dic,open('db','w'))
				        print('33[32m购票成功33[0m')
				    else:
				        print('33[31m购票失败33[0m')

				def task(lock):
				    search()
				    lock.acquire()  获得锁
				    get()
				    lock.release()  释放锁

				if __name__ == '__main__':
				    lock = Lock()
				    for i in range(100): #模拟并发100个客户端抢票
				        p=Process(target=task,args=(lock,))
				        p.start()

  

s:1.锁不要轻易的使用,容易造成死锁现象(我们写代码一般不会用到,都是内部封装好的)
2.锁只在处理数据的部分加来保证数据安全(只在争抢数据的环节加锁处理即可)

虽然可以用文件共享数据实现进程间通信,但问题是:
1.效率低(共享数据基于文件,而文件是硬盘上的数据)
2.需要自己加锁处理
因此我们最好找寻一种解决方案能够兼顾:1、效率高(多个进程共享一块内存的数据)2、帮我们处理好锁问题。这就是mutiprocessing模块为我们 提供的基于消息的IPC通信机制:队列和管道。

队列和管道都是将数据存放于内存中
队列又是基于(管道+锁)实现的,可以让我们从复杂的锁问题中解脱出来,

我们应该尽量避免使用共享数据,尽可能使用消息传递和队列,避免处理复杂的同步和锁问题,而且在进程数目增多时,往往可以获得更好的可获展性。
队列的介绍:
因为队列是管道的一种升级版所以在这里主要讲解一下队列的用法,首先提供队列的是mutiprocessing下的queue模块,通过实例化可以得到一个队列对象。
队列是规定了大小的一种容器,同时遵循先进先出顺序
队列.get()拿到队列里的值 如果队列里面没有值会阻塞
队列.put()向队列里存值 如果队列已经满了,不会报错会阻塞在当前状态
( 队列.full() 判断队列是否满了
队列.empty() 判断队列是否空了
队列.get_nowait() 不阻塞式的拿,抛异常
以上方法在多进程的情况下是不精确 )

生产者和消费者模型:

为什么要使用生产者和消费者模式
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。

什么是生产者消费者模式
生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。
生产者:生产/制造东西的
消费者:消费/处理东西的
该模型除了上述两个之外还需要一个媒介
生活中的例子做包子的将包子做好后放在蒸笼(媒介)里面,买包子的取蒸笼里面拿
厨师做菜做完之后用盘子装着给你消费者端过去
生产者和消费者之间不是直接做交互的,而是借助于媒介做交互

生产者(做包子的) + 消息队列(蒸笼) + 消费者(吃包子的)

在这里我们直接pass掉可能出现的问题直接使用最终版本,而最终版本我们需要引入一个模块JoinableQueue,在对这个模块进行实例化创建的时候除了队列的基础功能还为我们提供了额外的方法

q.task_done()
使用者使用此方法发出信号,表示q.get()返回的项目已经被处理。如果调用此方法的次数大于从队列中删除的项目数量,将引发ValueError异常。

q.join()
生产者将使用此方法进行阻塞,直到队列中所有项目均被处理。阻塞将持续到为队列中的每个项目均调用q.task_done()方法为止。

from multiprocessing import Process,JoinableQueue
import time,random,os
def consumer(q):
    while True:
        res=q.get()
        time.sleep(random.randint(1,3))
        print('33[45m%s 吃 %s33[0m' %(os.getpid(),res))
        q.task_done() #向q.join()发送一次信号,证明一个数据已经被取走了

def producer(name,q):
    for i in range(10):
        time.sleep(random.randint(1,3))
        res='%s%s' %(name,i)
        q.put(res)
        print('33[44m%s 生产了 %s33[0m' %(os.getpid(),res))
    q.join() #生产完毕,使用此方法进行阻塞,直到队列中所有项目均被处理。


if __name__ == '__main__':
    q=JoinableQueue()
    #生产者们:即厨师们
    p1=Process(target=producer,args=('包子',q))
    p2=Process(target=producer,args=('骨头',q))
    p3=Process(target=producer,args=('泔水',q))

    #消费者们:即吃货们
    c1=Process(target=consumer,args=(q,))
    c2=Process(target=consumer,args=(q,))
    c1.daemon=True
    c2.daemon=True

    #开始
    p_l=[p1,p2,p3,c1,c2]
    for p in p_l:
        p.start()

    p1.join()
    p2.join()
    p3.join()
    print('主') 
    
    #主进程等--->p1,p2,p3等---->c1,c2
    #p1,p2,p3结束了,证明c1,c2肯定全都收完了p1,p2,p3发到队列的数据
    #因而c1,c2也没有存在的价值了,不需要继续阻塞在进程中影响主进程了。应该随着主进程的结束而结束,所以设置成守护进程就可以了。

  

那么关于进程的知识点差不多就结束了,进程的优点很明显,再多道的逻辑下我们可以实现同一时间多个进程同时使用,完成了对空间最大化的利用但是随之我们得到了无法避免的缺点:1.进程在同一时间只能执行一个任务
2.如果进程需要进行IO交互就会挂起阻塞,那么其他不需要使用输入数据的进程也无法运行

这两个缺点是进程的本质所以无法通过优化完善,所以我们在这里又引入一种新的概念 线程。


什么是线程:我们在创建进程的时候会在内存中开辟一块空间,所以进程也是一种资源单位,在运行进程的时候回去调用进程中的线程,所以线程是真正的执行单位,每个进程都会自带一个线程。
ps(进程与线程都是虚拟单位,只是为了帮助我们解决问题而抽象化得量)

为什么要有线程:开线程的开销要远远小于开进程的开销。

原文地址:https://www.cnblogs.com/Jicc-J/p/12763829.html