python进阶--线程-通信

python进阶–线程-通信

1. 变量竞争

1.1 资源竞争问题

当两个线程对公共资源进行修改时,往往会出错。

下面是经典的线程资源竞争问题:

from threading import Thread

def add():
    global count
    for i in range(1000000):
        count +=1

def sub():
    global count
    for i in range(1000000):
        count -= 1

def test():
    th_add = Thread(target=add)
    th_sub = Thread(target=sub)
    th_add.start();th_sub.start()
    th_add.join();th_sub.join()
    print(count)

if __name__ == '__main__':
    count = 0
    test()

经测试输出的结果并非预料的0而是一个不确定的数。详情请搜索python线程资源竞争问题

1.2 解决方案

对于资源竞争问题,通常采用机制解决

from threading import Thread, Lock

def add():
    global count
    for i in range(1000000):
        lock.acquire()   #所有原子操作均在锁内进行
        count +=1
        lock.release()

def sub():
    global count
    for i in range(1000000):
        lock.acquire()
        count -= 1
        lock.release()

def test():
    th_add = Thread(target=add)
    th_sub = Thread(target=sub)
    th_add.start();th_sub.start()
    th_add.join();th_sub.join()
    print(count)

if __name__ == '__main__':
    count = 0
    lock = Lock()  #获得锁
    test()

机制的原理是:当lock.acquire方法执行后,其他线程不再获得cpu争夺权,直到lock.release方法执行后再次争夺cpu。即暂时停止多线程,等锁内操作执行完为止。

1.3 队列

对于线程的机制,Python的queue模块提供很好的底层帮助。我们先了解生产者-消费者模型。

在这里插入图片描述

把一个需要进程通信的问题分开考虑

生产者:只需要往队列里面放置产品(不需要关心如何消费,也不用关心是谁消费)

消费者:只需要从队列里面拿去产品(不需要关心如何生产,也不用关心是谁生产)

1.4 queue

queue 模块实现多生产者,多消费者队列。当信息必须安全的在多线程之间交换时,它在线程编程中是特别有用的。此模块中的 Queue 类实现了所有锁定需求的语义。

模块实现了三种类型的队列,它们的区别仅仅是条目取回的顺序。在 FIFO 队列(Queue)中,先添加的产品先取回。在 LIFO 队列(LifoQueue)中,最近被添加的条目先取回(操作类似一个堆栈)。优先级队列(PriorityQueue)中,条目将保持排序( 使用 heapq 模块 ) 并且最小值的条目第一个返回。

1.5 Queue

class Queue:
    def __init__(self, maxsize=0):
        pass

maxsize是一个整数,表示队列中允许存放的产平数目上限,如果产品数达到上限,那么停止产品压入队列。若maxsize<=0则队列内允许存放产品数目上线为无穷大。

队列对象 (Queue, LifoQueue, PriorityQueue)均提供如下方法:

1. qsize

返回队列大致长度。注意:返回值**>0**不保证后续的 get() 不被阻塞,返回值 <maxsize 不保证后续的 put() 不被阻塞。

2. empty

如果队列为空,返回 True ,否则返回 False 。如果 empty() 返回 True ,不保证后续调用的 put() 不被阻塞。类似的,如果 empty() 返回 False ,也不保证后续调用的 get() 不被阻塞。

3. full

如果队列是满的返回 True ,否则返回 False 。如果 full() 返回 True 不保证后续调用的 get() 不被阻塞。类似的,如果 full() 返回 False 也不保证后续调用的 put() 不被阻塞。

4 put
def put(item, block=True, timeout=None):
    pass

item:产品

block:阻塞/非阻塞 标志,默认为true即阻塞

timeout:允许的延时时间

方法用于将产品item压入队列,对于队列已满情况:

若 block=True,timeout=None:阻塞式、不限时,所以等待队列有空位(消费者消费)就将产品压入队列。

若 block=True,timeout=x:阻塞式、限时x秒,若x秒内无空位,抛出异常Full

若 block=False:非阻塞式,限时已经无意义,只要队列满了,压入失败立刻抛出异常Full

5. put_nowait

相当于put( item, False)

6. task_done

队列如何得知所有的产品都被接收和处理完毕呢,简单说队列有一个计数器,记录正在处理的产品数目,生产者压入一个产品计数器加1,消费者处理一个产品,计数器减一。那么队列如何得知消费者处理产品了呢?

消费者处理一个产品后,调用task_down方法,向队列发送消息,通知队列产品已处理,计数器减一。

所以,需要确保队列为空(比如使用join方法)时,每当消费者处理一个产品后都要调用他。

7. join

阻塞至队列中所有的产品都被接收和处理完毕。

举个栗子:

使用task_down

from threading import Thread, current_thread
from queue import Queue
import time
import random

qsize = 3
q = Queue(qsize)
def customer():  
    while(True):
        time.sleep(0.5)   #延时一下,便于查看
        print(current_thread().name, q.get());q.task_done()


def producer():
    while(True):
        c = random.choice('农夫山泉有点甜')
        q.put(c)
        print(current_thread().name, c)    #每次向对列压入两个产品
        c = random.choice('农夫山泉有点甜')
        q.put(c)
        print(current_thread().name, c)
        q.join()                           #队列不空就会阻塞不在运行

thp = Thread(target=producer, name='pro')  #生产者
thc = Thread(target=customer, name='cus')  #消费者
thc.start()
thp.start()

pro 山
pro 泉
cus 山
cus 泉
pro 山
pro 泉
cus 山
cus 泉
……

q.join() 后生产者被阻塞,只有等到消费者将队列”清空“后才会继续,所以生产者执行一次–>生产者卡主–>消费者执行两次–>队列已空、生产者执行一次–>生产者卡主……

不使用task_down

from threading import Thread, current_thread
from queue import Queue
import time
import random

qsize = 3
q = Queue(qsize)
def customer():  
    while(True):
        time.sleep(0.5)   #延时一下,便于查看
        print(current_thread().name, q.get())#;q.task_done() 不再使用


def producer():
    while(True):
        c = random.choice('农夫山泉有点甜')
        q.put(c)
        print(current_thread().name, c)    #每次向对列压入两个产品
        c = random.choice('农夫山泉有点甜')
        q.put(c)
        print(current_thread().name, c)
        q.join()                           #队列不空就会阻塞不在运行

thp = Thread(target=producer, name='pro')  #生产者
thc = Thread(target=customer, name='cus')  #消费者
thc.start()
thp.start()

pro 夫
pro 泉
cus 夫
cus 泉
#此时卡住了

卡主的原因很简单,消费者虽然处理了产品,队列”清空“,但是未调用task_down,计数器未减一,导致队列认为自己没清空,所以生产者在q.join()这一步永久堵塞,而消费者取不到产品也永久堵塞。

总之,join()的时候一定要用task_down,当然可以看出task_down对消费者消费产品无直接影响(消费者依旧可以取)。

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

从队列中取出一个产品

参数blocktimeout的含义与put方法雷同,用于处理空队列状况。

9 get_nowait

相当于get(False)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值