~~并发编程(五):重要方法~~

进击のpython

*****

并发编程——重要方法


前面提到的方法都是比较基本的,比较基础

这个小节我们着重了解一下以下几个方法:

1.守护进程

2.互斥锁

3.队列


守护进程

守护进程,就有点像古时候的封建奴隶制下的皇上与太监,皇上死了,太监就得陪着殉葬,守护着皇上

守护进程就是这样的关系,当主进程结束,守护进程随之消失

守护进程一般是怎么用呢?如果我们有两个进程并发执行,那我们就可以开一个主进程,再开一个子进程

但是呢,如果我们开的这个子进程在主进程结束之后就没什么用了,那这个时候就建议用守护进程了

p.daemon:默认值为False,如果设为True,代表p为后台运行的守护进程

当p的父进程终止时,p也随之终止,并且设定为True后,p不能创建自己的新进程

必须在p.start()之前设置

from multiprocessing import Process


def func(name, *args, **kwargs):
    print(f'{name}执行了')
    pass


if __name__ == '__main__':
    p = Process(target=func, args=('子进程',))
    # p.daemon = True
    p.start()
    print('我是主进程... ...')

正常开启子进程,都不是守护进程,由于声明之后还要有一段时间才能执行子进程

所以先打印 “我是主进程... ...” ,再打印 “子进程被执行了”

但是当我把注释的语句打开,p就变成了守护进程(注意,是在start上面)

由于还是主进程会先打印,就会导致,子进程还没打印就陪葬了

就只会打印“我是主进程... ...”


互斥锁

首先啊,原则上进程之间是不能够相互通信的,但是捏,进程之间还是共享一套文件资源的

所以访问同一个文件或者同一个打印终端,是没有问题的

但是计算机是贪婪地,有共享,就会有竞争

有竞争,如果没有限制,没有秩序,就会混乱

import time
from multiprocessing import Process


def func(name, *args, **kwargs):
    print(f'{name}正在执行.. ..')
    time.sleep(2)
    print(f'{name}执行完毕!')

    pass


if __name__ == '__main__':
    for i in range(3):
        p = Process(target=func, args=(f'进程{i}',))
        p.start()

我想达到的效果是不是应该是

进程0正在执行.. ..
进程0执行完毕!
进程1正在执行.. ..
进程1执行完毕!
进程2正在执行.. ..
进程2执行完毕!

而实际情况打印的是:

进程1正在执行.. ..
进程0正在执行.. ..
进程2正在执行.. ..
进程1执行完毕!
进程0执行完毕!
进程2执行完毕!

这就出现了竞争(多打印几次,还会出现别的结果)

就好像你去上厕所,但是大家都不排队,你挤进去了,别人还往里挤

还有的时候开门看看你,所以为了解决这个问题,我们一般都在卫生间加把锁

上厕所的时候锁一下门,我不完事,谁也别想进来

是对于程序也是,加一个锁,就让程序变得有序了

其实也就相当于把并发变成了串行,诚然降低了效率,但是保证了数据的安全

import time
from multiprocessing import Process, Lock


def func(name, lock, *args, **kwargs):
    lock.acquire()
    print(f'{name}正在执行.. ..')
    time.sleep(2)
    print(f'{name}执行完毕!')
    lock.release()
    pass


if __name__ == '__main__':
    lock = Lock()
    for i in range(3):
        p = Process(target=func, args=(f'进程{i}', lock))
        p.start()

lock = Lock(),对象的实例化,实例化出一个锁

然后锁,有关锁(acquire)就有开锁(release)

这样就保证某个子进程在用打印的时候,别的子进程只能在外面等待,就达到了我们的目的

抢票系统

这个经典的例子也可以通过这个抢票系统来帮助理解

首先所有人都可以查票,如果有票就可以买

我们可以先写一下

import json

from multiprocessing import Process


def search(name, *args, **kwargs):
    msg = json.load(open('db.json'))
    print(f'{name}查看票数,当前有{msg["count"]}张')
    pass


def get(name, *args, **kwargs):
    msg = json.load(open('db.json'))
    if msg["count"] > 0:
        msg["count"] -= 1
        json.dump(msg, open('db.json', 'w'))
        print(f'{name}买到票了,当前有{msg["count"]}张')


def main(name):
    search(name)
    get(name)


if __name__ == '__main__':
    for i in range(1, 5):
        p = Process(target=main, args=(f"第{i}个人",))
        p.start()

json.dump({'count': 1}, open("db.json", 'w'))

执行一下

第2个人查看票数,当前有1张
第2个人买到票了,当前有0张
第4个人查看票数,当前有1张
第3个人查看票数,当前有1张
第4个人买到票了,当前有0张
第3个人买到票了,当前有0张
第1个人查看票数,当前有1张
第1个人买到票了,当前有0张

问题出现了,只有一张票,但是我们却卖出去四张

这是不对的,应该是查询大家一起查,买票只能一个一个买

所以可以这样:

import json
import time

from multiprocessing import Process, Lock


def search(name, *args, **kwargs):
    msg = json.load(open('db.json'))
    print(f'{name}查看票数,当前有{msg["count"]}张')
    pass


def get(name,lock, *args, **kwargs):
    lock.acquire()
    msg = json.load(open('db.json'))
    if msg["count"] > 0:
        msg["count"] -= 1
        json.dump(msg, open('db.json', 'w'))
        print(f'{name}买到票了,当前有{msg["count"]}张')
    else:
        print(f'{name}没买到票!')
    lock.release()
def main(name,lock):
    time.sleep(1) # 模拟网络延迟
    search(name)
    get(name,lock)


if __name__ == '__main__':
    lock = Lock()

    for i in range(1, 5):
        p = Process(target=main, args=(f"第{i}个人",lock))
        p.start()

json.dump({'count': 1}, open("db.json", 'w'))

输出结果就是我们想的那样了,只有一个人买到了

第2个人查看票数,当前有1张
第2个人买到票了,当前有0张
第4个人查看票数,当前有0张
第4个人没买到票!
第3个人查看票数,当前有0张
第3个人没买到票!
第1个人查看票数,当前有0张
第1个人没买到票!

横向对比join方法,在刚开始的时候我们也利用join方法进行了等待的操作

我们看看用join怎么做

import json
import time

from multiprocessing import Process, Lock


def search(name, *args, **kwargs):
    msg = json.load(open('db.json'))
    print(f'{name}查看票数,当前有{msg["count"]}张')
    pass


def get(name, lock, *args, **kwargs):
    lock.acquire()
    msg = json.load(open('db.json'))
    if msg["count"] > 0:
        msg["count"] -= 1
        json.dump(msg, open('db.json', 'w'))
        print(f'{name}买到票了,当前有{msg["count"]}张')
    else:
        print(f'{name}没买到票!')
    lock.release()


def main(name, lock):
    time.sleep(1)  # 模拟网络延迟
    search(name)
    get(name, lock)


if __name__ == '__main__':
    lock = Lock()

    for i in range(1, 5):
        p = Process(target=main, args=(f"第{i}个人", lock))
        p.start()
        p.join()

发现也可以达到我哦们想要的效果,但是查票的过程也变成串行了

很明显大家查票时应该是并发地去查询而无需考虑数据准确与否

此时join与互斥锁的区别就显而易见了:

join是将一个任务整体串行,而互斥锁的好处则是可以将一个任务中的某一段代码串行

比如只让main函数中的get任务串行


队列

进程彼此之间互相隔离,要实现进程间通信(IPC)

multiprocessing模块支持两种形式:队列和管道,这两种方式都是使用消息传递的

是消息传递,不是大数据!

看一下队列中有什么

class Queue(object):
    def __init__(self, maxsize=-1):
        self._maxsize = maxsize

maxsize:可放入的允许的最大项数,如果不设置,默认无限

​ 但是队列是基于内存的,真正的无限其实取决于内存

 def qsize(self):
        return 0

    def empty(self):
        return False

    def full(self):
        return False

    def put(self, obj, block=True, timeout=None):
        pass

    def put_nowait(self, obj):
        pass

    def get(self, block=True, timeout=None):
        pass

    def get_nowait(self):
        pass

    def close(self):
        pass

    def join_thread(self):
        pass

    def cancel_join_thread(self):
        pass

队列的方法大概是上述这些

put():往队列里放信息

get():从队列里拿数据

empty():判断队列是否为空

full():判断队列满没满

同时也要记住,队列是“先进先出”

当队列满了,再往里放,就会阻塞

当队列空了,再往外取,也会阻塞

from multiprocessing import Queue

q = Queue(3)
q.put(1)
q.put(2)
q.put(3)
print(q.full())

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

可以看到打印出来的是1 2 3

因为我们放的顺序就是1 2 3


*****
*****
原文地址:https://www.cnblogs.com/jevious/p/11402260.html