python进程和线程(四)

线程同步条件、信号量及队列

同步条件(event)

        下面是官方文档对event的一些解释:

An event is a simple synchronization object;

the event represents an internal flag, and threads
can wait for the flag to be set, or set or clear the flag themselves.

event = threading.Event()

# a client thread can wait for the flag to be set
event.wait()

# a server thread can set or reset it
event.set()
event.clear()
If the flag is set, the wait method doesn’t do anything.
If the flag is cleared, wait will block until it becomes set again.
Any number of threads may wait for the same event

          也就是说:

event.isSet():返回event的状态值;

event.wait():如果 event.isSet()==False将阻塞线程;

event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;

event.clear():恢复event的状态值为False。

          通过Event,我们可以实现两个或多个线程间的交互,下面是一个红绿灯的例子,即起动一个线程做交通指挥灯,生成几个线程做车辆,车辆行驶按红绿灯的规则。

 View Code

        运行之后就会看到4辆车在红绿灯时的状态了,非常简单,再看一个小例子就行了

 View Code

信号量(Semaphore)

       之前讲的同步锁,是只允许一个线程去操作数据,而Semaphore就是同时允许一定数量的线程去操作数据。怎么说呢,比如100个人去洗脚城洗脚,只有五个技师,那一次只能同时五个人洗脚,后面的就只能排队等着了。原理也比较简单,就是BoundedSemaphore或Semaphore管理一个内置的计数 器,每当调用acquire()时-1,调用release()时+1。计数器不能小于0,当计数器为 0时,acquire()将阻塞线程至同步锁定状态,直到其他线程调用release()。

      BoundedSemaphore与Semaphore的唯一区别在于前者将在调用release()时检查计数 器的值是否超过了计数器的初始值,如果超过了将抛出一个异常。

 View Code

      运行后,也可能出现我们同步锁里面说的并排打印的问题,这个就要看你个人需求了。

线程队列(queue)

      之前我们在学校学过什么堆栈、队列的知识,在这里,没有堆栈这个概念,只有队列。

创建一个“队列”对象
import Queue
q = Queue.Queue(maxsize = 10)
Queue.Queue类即是一个队列的同步实现。队列长度可为无限或者有限。可通过Queue的构造函数的可选参数maxsize来设定队列长度。如果maxsize小于1就表示队列长度无限。

将一个值放入队列中
q.put(10)
调用队列对象的put()方法在队尾插入一个项目。put()有两个参数,第一个item为必需的,为插入项目的值;第二个block为可选参数,默认为
1。如果队列当前为空且block为1,put()方法就使调用线程暂停,直到空出一个数据单元。如果block为0,put方法将引发Full异常。

将一个值从队列中取出
q.get()
调用队列对象的get()方法从队头删除并返回一个项目。可选参数为block,默认为True。如果队列为空且block为True,get()就使调用线程暂停,直至有项目可用。
如果队列为空且block为False,队列将引发Empty异常。

Python Queue模块有三种队列及构造函数:
1、Python Queue模块的FIFO队列先进先出。  class queue.Queue(maxsize)
2、LIFO类似于堆,即先进后出。             class queue.LifoQueue(maxsize)
3、还有一种是优先级队列级别越低越先出来。   class queue.PriorityQueue(maxsize)

此包中的常用方法(q = Queue.Queue()):
q.qsize() 返回队列的大小
q.empty() 如果队列为空,返回True,反之False
q.full() 如果队列满了,返回True,反之False
q.full 与 maxsize 大小对应
q.get([block[, timeout]]) 获取队列,timeout等待时间
q.get_nowait() 相当q.get(False)
非阻塞 q.put(item) 写入队列,timeout等待时间
q.put_nowait(item) 相当q.put(item, False)
q.task_done() 在完成一项工作之后,q.task_done() 函数向任务已经完成的队列发送一个信号
q.join() 实际上意味着等到队列为空,再执行别的操作

        现在有一个需求,比如我有一个列表,要用多线程去移除列表最后一个元素,我们一般是这么写:

 View Code

       但是运行过程中我们会发现报错,ValueError: list.remove(x): x not in list,同时操作这个元素的时候,发现这个元素已经被其他线程移除了,所以报错。这里想说明的是,列表在多线程中的操作是不安全的。

       下面这个例子,是通过queue,来实现一个生产者和消费者模型,也就是说边做边吃

 View Code

       A是生产者,一直在做包子,做20个,B和C就是消费者,就是一直在吃包子,没有包子就喊。这里我们用的是q.empty()来判断队列中有没有包子,我们也可以通过q.task_done()和q.join()来完成这件事:

 View Code

       就是说A君做好包子了,告诉队列,顾客这边在jion状态,包子没做好就一直等着,知道队列收到了包子做好的消息,也会告诉顾客能吃包子了,所以这里q.task_done()和q.join()是成对出现的,单个使用没什么意义,你也可以试试在厨师和顾客中调换位置使用。

       线程的知识就讲到这里了,下面要说的是进程的内容,如果前面线程都掌握了,进程就相当简单,大同小异了。

猜你喜欢

转载自blog.csdn.net/c710473510/article/details/89362200