守护进程,互斥锁, IPC ,Queue队列,生产消费着模型

1.守护进程

什么是守护进程?

  进程是一个正在运行的程序

  守护进程也是一个普通进程,意思是一个进程可以守护另一个进程,比如如果b是a的守护进程,a是被守护的进程,如果a进程结束,b进程也会随之结束。

使用场景:

  父进程交给了子进程一个任务,子进程在执行过程中并没有执行完毕,但是父进程结束了,那么子进程就没又继续执行的意义了

使用方法:在start 前面加 deamon = Ture

案例:

from multiprocessing import Process
import time
def task():
    print('子进程开启了')
    time.sleep(5)
    print('你跟着去吧')

if __name__ == '__main__':
    print('父进程开始了')
    p = Process(target=task)

    # 使用daemon方法来守护父进程,如果父进程提前结束了
    # 那么子进程也会一起结束
    p.daemon = True
    p.start()
    time.sleep(2)
    print('父进程结束了')

2.互斥锁

什么是互斥锁?

  互斥锁,意思是互相排斥的锁,在程序中的意思是。如果这个资源已经被锁定了,那么其他的进程则无法使用该资源

PS: 锁,并不是真的把资源锁起来了,只是在代码层面限制你的代码不能执行

为什么需要互斥锁?

  并发带来资源的竞争问题:

当多个进程同时要操作同一个资源时,将会导致数据错乱的问题,那么解决这样问题的方案有两种

  1. 是用 join ,等子进程结束后再执行其他进程,这样做的效果确实能实现资源竞争的问题,但是却降低了效率,如果这样操作就没有必要开启子进程了,而且本来多个进程之间是公平竞争的,而join方法,就必须按照join的执行顺序来进行,所以这是不合理的

  2. 给公共资源加锁  

  每个进程间是公平的,都是同时竞争,但是如果有一个进程抢到了资源,就会把资源锁住,那么其他的程序就只能等哪个进程释放锁之后才能使用。

锁与join的区别

  1. join是固定了执行顺序,不仅会造成父进程等待子进程结束后才能继续执行,而且浪费资源

  而锁是公平竞争,谁先抢到谁先执行,父进程依然可以继续执行,不受干扰

  2.最主要i的区别

  join是把进程的任务全部串行

  锁可以锁任意代码,可以自主的调整锁定的粒度

使用方法:

  在你需要执行的代码前加 acpuire 方法,代码的后面加 release

案例:

'''
并发将带来资源的竞争问题
当多个进程同时要操作同一个资源时,将会导致数据错乱的问题

'''


def task1(lock):
    # acquire 将进程上锁(并不是上锁),是让其他进程必须等抢到系统资源
    # 的进程执行完毕后才能执行
    lock.acquire()
    print('哒哒哒哒哒哒冒蓝光的加特林')
    print('biubiubiubiu冒红光的M416')
    print('砰砰砰砰砰砰冒黄光的手榴弹')
    # 当进程执行完后要释放锁,让其他进程使用
    lock.release()


def task2(lock):
    lock.acquire()
    print('冒蓝光的加特林')
    print('冒红光的M416')
    print('黄光的手榴弹')
    lock.release()


def task3(lock):
    lock.acquire()
    print('加特林')
    print('M416')
    print('手榴弹')
    lock.release()


if __name__ == '__main__':
    # 需要先实例化得到一把锁
    # 需要注意的是,不要对同一把执行两次acquire,会锁死
    # 导致程序无法运行,一此acquire必须对应一次release
    lock = Lock()
    p1 = Process(target=task1, args=(lock,))
    p2 = Process(target=task2, args=(lock,))
    p3 = Process(target=task3, args=(lock,))

    p1.start()
    p2.start()
    p3.start()

3.IPC

IPC = 进程间通讯 (通讯指的是互相交换数据)

进程之间内存是相互隔离的,当一个进程想要把数据给另外一个进程的时候,就需要考虑IPC

进程之间传输数据的方式:

  1.管道:只能单向通讯,数据都是二进制(subprocess)

  2.文件:在银盘上创建共享文件

    缺点: 速度慢

       优点: 数据量没有限制

  3.socket

    编程难度复杂

  4. 共享内存 

    优点: 速度块

    缺点: 数据量不能太大

共享内存的方式

Manager类

  Manager提供很多数据结构 list dict 等等

  Manager所创建出来的数据结构,具备进程间共享的特点

案例:

from multiprocessing import Process,Manager,Lock
def task(data, lock):
    lock.acquire()
    num = data['num']

    data['num'] = num - 1
    lock.release()


if __name__ == '__main__':
    # Manager 开启一个共享字典,这个字典子进程与父进程公同拥有
    m = Manager()
    data = m.dict({'num': 10})
    lock = Lock()

    for i in range(10):
        p = Process(target=task, args=(data, lock))
        p.start()
        p.join()
    print(data)

PS: 需要强调的是Manager创建的一些数据结构是不带锁的,可能会出现问题

4.Queue队列  *****(重点)

Queue队列帮我们处理了锁的问题,重点

  队列是一种特殊的数据结构,先存储的先取出,先进先出

  相反的是堆区,先存储的后取出,先进后出

PS:函数嵌套调用时,执行顺序是先进后出的,所以也称之为函数栈

  调用函数时,函数入栈,函数结束就出栈

案例:

from multiprocessing import Queue

# 创建队列,不指定maxsize 则没有数量限制
q = Queue(3)

# 存储元素
q.put('abc')
q.put('123')
q.put('哈哈哈')

print(q.get())
print(q.get())
print(q.get())

# 如果容量已经满了,在调用put时将会进入阻塞状态
# 直到有人从队列中拿走数据有空位置才会继续执行


# 如果队列已经空了,在调用get时将进入阻塞状态,
# 直到有人存储了新的数据到队列中,才会继续


# block 表示是否需要进行阻塞,默认是阻塞的,当设置为False,并且队列为空时,则抛出异常
# timeout 表示阻塞的超时时间,超过时间还是没有值或还是没位置则抛出异常,仅在block为True有效

# 此处如果没有值,则会阻塞两秒,如果还没有值,则报异常
# q.get(block=True,timeout=2)

# 此处存值时,如果容器满了,则不会阻塞,直接报异常
# q.put('123', block=False)

5.生产消费着模型

生产者:产生数据的一方

消费者:处理数据的一方

生产者与消费者中存在的问题:

生产者和消费者处理速度不平衡,一方快一方慢,导致一方需要等待另一方

双方是耦合在一起的,消费者必须等待生产者生成完毕后才能开始处理,

但是如果消费者消费速度太慢,生产者必须等待其处理完毕才能开始生成下一个数据

解决途径:

  将双方分开,生产者专门生产,消费者专门消费,两者不需要等待某一方结束后才能执行

但是这样一来就需要一个共同的容器,生产者完成后放入容器,消费者从容器中取出数据,这样就解决了双方能力不平衡的问题,完成时间快的一方继续完成,无需等待另一方

案例:

from multiprocessing import Process, Queue, Lock
import time, random, os

# 消费者
def consumer(q):
    # 假如消费者点了10盘菜
    for i in range(10):
        # 因为吃饭需要时间,所以使用tiem模拟了时间
        time.sleep(random.randint(0, 2))
        # 通过从Queue容器获取生产者的数据
        rose = q.get()
        print('%s已经吃完了%s' % (os.getpid(), rose))


# 生产者
def producer(q):
    # 生产者需要烧制10盘菜
    for i in range(10):
        # 因为制作菜需要时间,所以也使用了tiem 模拟了时间
        time.sleep(random.randint(0, 2))
        print('第%s盘菜烧好了' % i)
        rose = ('第%s盘菜' % i)
        # 使用put方法往Queue中添加数据
        q.put(rose)

if __name__ == '__main__':
    # 创造一个Queue来存储数据
    q = Queue()
    c = Process(target=consumer,args=(q,))
    p = Process(target=producer,args=(q,))
    c.start()
    p.start()
原文地址:https://www.cnblogs.com/liguodeboke/p/10969684.html