day34---并发编程之扩展

1. 守护进程与守护线程

  • 无论是守护进程守护线程,都会在主进程或控制线程运行结束后终止,但不代表程序结束。

  • 守护进程的特点:

    • 主进程代码运行完毕,守护进程就会结束
    • 主进程在程序代码全部运行结束后回收守护进程的资源,然后等待其他子进程运行结束后回收资源,直到所有子进程全部回收完再结束
    • 只要主进程没有运行完,会一直运行守护进程
    • 守护进程必须在子进程开启之前启用
    • 守护进程不能再开启子进程
    # 守护进程
    import os
    import time
    from multiprocessing import Process
    def task1():
            print('task1 %s start...' % os.getpid())
            time.sleep(1)
            print('task1 %s stop...' % os.getpid())
    def task2():
            print('task2 %s start...' % os.getpid())
            time.sleep(3)
            print('task2 %s stop...' % os.getpid())
    if __name__ == '__main__':
            p1 = Process(target=task1)
            p2 = Process(target=task2)
            p1.daemon = True
            p2.daemon = True
            p1.start()
            p2.start()
            time.sleep(2)
            print('yan...')
    >>>
    task1 2112 start...
    task2 5844 start...
    task1 2112 stop...
    yan...
    
  • 守护线程的特点:

    • 控制线程所在的进程中所有非守护线程全部都运行完,守护线程才会结束
    • 守护线程运行完而其他线程没有运行完,会先回收守护线程的资源,直到所有线程运行完之后回收所有资源才结束
    • 守护线程没有运行完,而其他非守护线程已经运行完,会回收其他线程的资源后结束
    # 守护线程
    import time
    from threading import Thread, current_thread
    def task1():
            print('task1 %s start...' % current_thread().getName())
            time.sleep(1)
            print('task1 %s stop...' % current_thread().getName())
    def task2():
            print('task2 %s start...' % current_thread().getName())
            time.sleep(3)
            print('task2 %s stop...' % current_thread().getName())
    if __name__ == '__main__':
            t1 = Thread(target=task1)
            t2 = Thread(target=task2)
            t1.daemon = True
            t2.daemon = True
            t1.start()
            t2.start()
            time.sleep(2)
            print('yan...')
    >>>
    task1 Thread-1 start...
    task2 Thread-2 start...
    task1 Thread-1 stop...
    yan...
    

2. 互斥锁

  • 加锁:acquire()

  • 释放锁:release()

  • with:加锁与释放锁

  • 进程的互斥锁:

    • 说明:进程之间数据无法共享,但是可以共享同一套系统、同一个文件、同一个终端,但是多进程由于处理时间的先后数序会出现结果错乱的情况,控制的方式就是加锁处理
    • 不使用锁的情况:并发运行效率高,但是输出和修改的数据是错乱的,安全性不可靠
    # 没有加锁的状态
    import os
    import time
    import random
    from multiprocessing import Process
    def task():
            print('%s ---> 111' % os.getpid())
            time.sleep(random.randint(1, 3))
            print('%s ---> 222' % os.getpid())
            time.sleep(random.randint(1, 3))
            print('%s ---> 333' % os.getpid())
    if __name__ == '__main__':
            p1 = Process(target=task)
            p2 = Process(target=task)
            p3 = Process(target=task)
            p1.start()
            p2.start()
            p3.start()
            print('yan...')
    >>>
    yan...
    7772 ---> 111
    4868 ---> 111
    7980 ---> 111
    7980 ---> 222
    4868 ---> 222
    7980 ---> 333
    7772 ---> 222
    4868 ---> 333
    7772 ---> 333
    
    • 使用锁的情况:加锁后由并发变成了串行,运行效率降低了,提高了安全性
    # 加锁后的状态
    import os
    import time
    import random
    from multiprocessing import Process, Lock
    def task(mutex):
            mutex.acquire()
            print('%s ---> 111' % os.getpid())
            time.sleep(random.randint(1, 3))
            print('%s ---> 222' % os.getpid())
            time.sleep(random.randint(1, 3))
            print('%s ---> 333' % os.getpid())
            time.sleep(random.randint(1, 3))
            mutex.release()
    if __name__ == '__main__':
            mutex = Lock()
            p1 = Process(target=task, args=(mutex,))
            p2 = Process(target=task, args=(mutex,))
            p3 = Process(target=task, args=(mutex,))
            p1.start()
            p2.start()
            p3.start()
            print('yan...')
    >>>
    yan...
    7512 ---> 111
    7512 ---> 222
    7512 ---> 333
    9448 ---> 111
    9448 ---> 222
    9448 ---> 333
    11672 ---> 111
    11672 ---> 222
    11672 ---> 333
    
    # 模拟抢票
    # 'db.txt文件内容:'{"count": 6}
    import time
    import random
    import json
    from multiprocessing import Process, Lock
    def search():
            with open('db.txt', encoding='utf-8') as f:
                db_dict = json.load(f)
                print('当前剩余票数是:%s' % db_dict['count'])
    def rob():
            with open('db.txt', encoding='utf-8') as fr:
                db_dict = json.load(fr)
                if db_dict['count'] > 0:
                    time.sleep(random.randint(1, 3))
                    db_dict['count'] -= 1
                    with open('db.txt', 'w', encoding='utf-8') as fw:
                        json.dump(db_dict, fw)
                        print('抢票成功!')
                else:
                    print('没有票了...')
    def task(mutex):
            search()
            with mutex:
                rob()
    if __name__ == '__main__':
            mutex = Lock()
            for i in range(10):
                p = Process(target=task, args=(mutex,))
                p.start()
    >>> 'db.txt文件内容是:'{"count": 0}
    当前剩余票数是:6
    当前剩余票数是:6
    当前剩余票数是:6
    当前剩余票数是:6
    当前剩余票数是:6
    当前剩余票数是:6
    当前剩余票数是:6
    当前剩余票数是:6
    当前剩余票数是:6
    当前剩余票数是:6
    抢票成功!
    抢票成功!
    抢票成功!
    抢票成功!
    抢票成功!
    抢票成功!
    没有票了...
    没有票了...
    没有票了...
    没有票了...
    
  • 线程的互斥锁:

    • 说明:线程之间本身就支持数据共享的,但是线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来
import time
import random
from threading import Thread, current_thread, Lock
def task():
        with mutex:
            print('%s --> 111' % current_thread().getName())
            time.sleep(random.randint(1, 3))
            print('%s --> 222' % current_thread().getName())
            time.sleep(random.randint(1, 3))
            print('%s --> 333' % current_thread().getName())
            time.sleep(random.randint(1, 3))
if __name__ == '__main__':
        mutex = Lock()
        t1 = Thread(target=task)
        t2 = Thread(target=task)
        t3 = Thread(target=task)
        t1.start()
        t2.start()
        t3.start()
        print('yan。。。')
>>>
Thread-1 --> 111
yan。。。
Thread-1 --> 222
Thread-1 --> 333
Thread-2 --> 111
Thread-2 --> 222
Thread-2 --> 333
Thread-3 --> 111
Thread-3 --> 222
Thread-3 --> 333

3. 信号量

  • 信号量跟互斥锁的意义类似,互斥锁是多个进程或线程抢占一个,信号量是可以指定抢占多个

  • 信号量跟池的不同是,信号量牵扯的是加锁的概念。池的只允许开启指定个数的进程或线程,而信号量是直接已经启用了多个进程或线程,通过设置的信号量来指定同时工作的进程或线程个数

# 进程的信号量
import os
import time
import random
from multiprocessing import Process, Semaphore
def task(sem):
        with sem:
            print('%s is doing...' % os.getpid())
            time.sleep(random.randint(1, 3))
if __name__ == '__main__':
        sem = Semaphore(4)
        for i in range(10):
            p = Process(target=task, args=(sem,))
            p.start()
        print('yan...')
>>>
8508 is doing...
10360 is doing...
10036 is doing...
yan...
4180 is doing...
5240 is doing...
8168 is doing...
3420 is doing...
10108 is doing...
10544 is doing...
9396 is doing...
# 线程的信号量
import time
import random
from threading import Thread, current_thread, Semaphore
def task(sem):
        sem.acquire()
        print('%s is doing...' % current_thread().getName())
        time.sleep(random.randint(1, 3))
        sem.release()
if __name__ == '__main__':
        sem = Semaphore(3)
        for i in range(10):
            t = Thread(target=task, args=(sem, ))
            t.start()
        print('yan...')
>>>
Thread-1 is doing...
Thread-2 is doing...
Thread-3 is doing...
yan...
Thread-5 is doing...
Thread-4 is doing...
Thread-6 is doing...
Thread-7 is doing...
Thread-8 is doing...
Thread-9 is doing...
Thread-10 is doing...

4. 管道

  • 说明:管道可以用于双向通信,利用通常在客户端/服务器中使用的请求响应模型或远程过程调用,就可以使用管道编写与进程交互的程序

  • 作用:可以让进程之间通信

5. 队列

  • 原理:管道和锁结合的方式实现,队列中可以存放所有数据类型的数据

  • 作用:实现进程之间的通信

  • 进程的队列需要从multiprocessing中导入Queue模块,线程的队列可以直接导入queue模块

  • 特点:

    • 队列:先进先出
    • 堆栈:先进后出(后进先出)
    • 优先级队列:根据指定的优先级输出,优先级低的先出(优先级队列是以元组的方式put,get出的结果也是元组)
  • 堆栈和优先级只存在在线程的队列中

  • 方法:

    • put():添加数据到队列中
    • get():从队列中读取并删除数据
    • empty():查看队列是否为空
    • full():查看队列是否已满
    • qsize():查看队里中的有效数据的数量
    • close():关闭队列
    # 进程的队列
    from multiprocessing import Queue
    q = Queue(3)
    q.put('yy')
    q.put([1, 2, 3, 4, 5])
    q.put({'name': 'yan', 'age': 18})
    print(q.empty())
    print(q.full())
    print(q.get())
    print(q.get())
    print(q.get())
    print(q.empty())
    print(q.full())
    >>>
    False
    True
    yy
    [1, 2, 3, 4, 5]
    {'name': 'yan', 'age': 18}
    True
    False
    
    # 线程的列队
    import queue
    q = queue.Queue(3)
    q.put('yy')
    q.put([1, 2, 3, 4, 5])
    q.put({'name': 'yan', 'age': 18})
    print(q.empty())
    print(q.full())
    print(q.get())
    print(q.get())
    print(q.get())
    print(q.empty())
    print(q.full())
    >>>
    False
    True
    yy
    [1, 2, 3, 4, 5]
    {'name': 'yan', 'age': 18}
    True
    False
    
    # 线程的堆栈
    import queue
    q = queue.LifoQueue(3)
    q.put('yy')
    q.put([1, 2, 3, 4, 5])
    q.put({'name': 'yan', 'age': 18})
    print(q.get())
    print(q.get())
    print(q.get())
    >>>
    {'name': 'yan', 'age': 18}
    [1, 2, 3, 4, 5]
    yy
    
    # 线程的优先级队列
    import queue
    q = queue.PriorityQueue(3)
    q.put((0, 'yy'))
    q.put((-1, [1, 2, 3, 4, 5]))
    q.put((0.5, {'name': 'yan', 'age': 18}))
    print(q.get())
    print(q.get())
    print(q.get())
    >>>
    (-1, [1, 2, 3, 4, 5])
    (0, 'yy')
    (0.5, {'name': 'yan', 'age': 18})
    

6. 生产者消费者模型

  • 描述:在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度

  • 原因:在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者

  • 说明:生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力

  • 作用:实现程序的解耦

  • 过程:

    • 1. 生产者把数据放在队列中
    • 2. 消费者从队列中取走数据
    # 生产者消费者模型总结
            # 程序中有两类角色:
                一类负责生产数据(生产者)
                一类负责处理数据(消费者)
            # 引入生产者消费者模型位了解决的问题:
                平衡生产者和消费者之间的速度差
            # 实现方式:
                生产者 --> 队列 --> 消费者
    
  • 生产者消费者模型应用

# 生产者:生产包子
# 消费者:吃包子
import os
import time
import random
from multiprocessing import Process, Queue
def producter(q):
        for i in range(10):
            res = '包子%s' % (i + 1)
            time.sleep(1)
            q.put(res)
            print('<%s> 生产了 ---> [%s]' % (os.getpid(), res))
def consumer(q):
        while True:
            res = q.get()
            if res == None:
                break
            print('<%s> 吃了 [%s]' % (os.getpid(), res))
            time.sleep(random.randint(1, 3))
if __name__ == '__main__':
        q = Queue()
        p = Process(target=producter, args=(q, ))
        c = Process(target=consumer, args=(q, ))
        p.start()
        c.start()
        p.join()
        q.put(None)
        c.join()
        print('yan...', os.getpid())
>>>
<11024> 生产了 ---> [包子1]
<3728> 吃了 [包子1]
<11024> 生产了 ---> [包子2]
<11024> 生产了 ---> [包子3]
<3728> 吃了 [包子2]
<11024> 生产了 ---> [包子4]
<11024> 生产了 ---> [包子5]
<3728> 吃了 [包子3]
<11024> 生产了 ---> [包子6]
<11024> 生产了 ---> [包子7]
<11024> 生产了 ---> [包子8]
<3728> 吃了 [包子4]
<11024> 生产了 ---> [包子9]
<11024> 生产了 ---> [包子10]
<3728> 吃了 [包子5]
<3728> 吃了 [包子6]
<3728> 吃了 [包子7]
<3728> 吃了 [包子8]
<3728> 吃了 [包子9]
<3728> 吃了 [包子10]
yan... 5124

7. 生产者消费者模型进阶

  • JoinableQueue:队列允许消费者通知生成者项目已经被成功处理;通知进程是使用共享的信号和条件变量来实现的。JoinableQueue对象的实例跟Queue对象的使用方法一样

  • maxsize:队列中允许最大值,默认为不限制

  • join():生产者调用之后进行阻塞,直到队列中所有的值都被处理完

  • task_done():消费者调用之后发出信号,表示队列中的值已经都处理完了

import os
import time
import random
from multiprocessing import Process, JoinableQueue
def producer(name, q):
        for i in range(3):
            res = '%s%s' % (name, i + 1)
            time.sleep(1)
            q.put(res)
            print('<%s> 生产了 ---> [%s]' % (os.getpid(), res))
        q.join()
def consumer(q):
        while True:
            res = q.get()
            time.sleep(random.randint(1, 3))
            print('<%s> 吃了 [%s]' % (os.getpid(), res))
            q.task_done()
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
        p1.start()
        p2.start()
        p3.start()
        c1.start()
        c2.start()
        p1.join()
        p2.join()
        p3.join()
        print('yan...', os.getpid())
>>>
<200> 生产了 ---> [包子1]
<1084> 生产了 ---> [煎饼1]
<7680> 生产了 ---> [油条1]
<11748> 吃了 [包子1]
<200> 生产了 ---> [包子2]
<1084> 生产了 ---> [煎饼2]
<7680> 生产了 ---> [油条2]
<200> 生产了 ---> [包子3]
<1084> 生产了 ---> [煎饼3]
<7680> 生产了 ---> [油条3]
<11748> 吃了 [油条1]
<6972> 吃了 [煎饼1]
<11748> 吃了 [包子2]
<6972> 吃了 [煎饼2]
<11748> 吃了 [油条2]
<11748> 吃了 [煎饼3]
<11748> 吃了 [油条3]
<6972> 吃了 [包子3]
yan... 2312
posted @ 2017-12-04 18:45  _岩哥  阅读(99)  评论(0)    收藏  举报