在多线程环境下,如果多个线程同时对于某个数据进行修改,则可能出现不可预料的后果,为了保证数据被正确修改。就需要对多个线程进行同步。最经典的就是生产者消费者模型。
生产者消费者问题
生产者消费者问题,是一个多线程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题,常用的方法有信号灯法等。如果解决方法不够完善,则容易出现活锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。
生产者消费者的优点:
- 解耦:
假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。 - 并发
由于生产者与消费者是两个独立的并发体,他们之间是用缓冲区通信的,生产者只需要往缓冲区里丢数据,就可以继续生产下一个数据,而消费者只需要从缓冲区拿数据即可,这样就不会因为彼此的处理速度而发生阻塞。 - 支持忙闲不均
缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。 - -
多线程同步
简单线程同步
使用Thread对象的Lock和RLock可以实现简单的线程同步。Lock和RLock对象都具有acquire和release方法。如果某一数据在某一时刻只允许一个线程进行操作,则可以将操作过程放在acquire和release方法中间。说简单同步的原因是如果一个ziyuan,下面是实现的一个简单的线程同步:
import threading
import time
class Mythread(threading.Thread):
def __init__(self,threadname):
threading.Thread.__init__(self,name=threadname)#调用Thread类的构造函数
def run(self):
global x
lock.acquire()
for i in range(3):
x=x+1
time.sleep(1)
print(x)
lock.release()
lock=threading.Lock()
t1=[]
for i in range(10):
t=Mythread(str(i))
t1.append(t)
x=0
for i in t1:
i.start()
在上述代码中创建了10个线程,每个线程都对全局变量x进行操作,然后打印x。每个线程对x进行的操作是对x+3。结果如上图所示。若将acquire和release删除了,最后结果为下图。其原因是在第一个线程执行完+3操作之后,还没来得及打印,就先休眠了。然后另一个线程接着访问x。以此类对。直到所有的线程多执行完x=x+3操作之后,然后再执行打印操作。此时x已经变为了30
使用条件变量保持同步
Python的Condition对象提供对复杂线程同步的支持。使用condition对象可以在某些事件之后才触发对数据的处理。下面是经典的生产者消费者问题。
import threading
class Producer(threading.Thread):
def __init__(self,threadname):
threading.Thread.__init__(self,name=threadname)
def run(self):
global x
con.acquire()
if x==10000:
con.wait()
pass
else:
for i in range(10000):
x+=1
con.notify()
print(x)
con.release()
class Consumer(threading.Thread):
def __init__(self,threadname):
threading.Thread.__init__(self,name=threadname)
def run(self):
global x
con.acquire()
if x ==0:
con.wait()
pass
else:
for i in range(10000):
x=x-1
con.notify()
print(x)
con.release()
con=threading.Condition()
x=0
p=Producer('Producer')
c=Consumer('Consumer')
p.start()
c.start()
p.join()
c.join()
使用queue实现线程同步
Python的Queue模块提供一种适用于多线程编程的FIFO实现。它可用于在生产者(producer)和消费者(consumer)之间线程安全(thread-safe)地传递消息或其它数据,因此多个线程可以共用同一个Queue实例。Queue的大小(元素的个数)可用来限制内存的使用。并且Queue模块已经实现了同步与互斥操作。所以可以实现一对一的生产者消费者模式,也可实现多对多的·生产者消费者模式。
使用继承Thread类实现生产者消费者模式:
from queue import Queue
import random,threading,time
#生产者类
class Producer(threading.Thread):
def __init__(self, name,queue):
threading.Thread.__init__(self, name=name)
self.data=queue
def run(self):
for i in range(5):
print("%s is producing %d to the queue!" % (self.getName(), i))
self.data.put(i)
time.sleep(random.randrange(2))
print("%s finished!" % self.getName())
#消费者类
class Consumer(threading.Thread):
def __init__(self,name,queue):
threading.Thread.__init__(self,name=name)
self.data=queue
def run(self):
for i in range(5):
val = self.data.get()
print("%s is consuming. %d in the queue is consumed!" % (self.getName(),val))
time.sleep(random.randrange(2))
print("%s finished!" % self.getName())
def main():
queue = Queue()
producer = Producer('Producer',queue)
consumer = Consumer('Consumer',queue)
producer.start()
consumer.start()
producer.join()
consumer.join()
print('All threads finished!')
if __name__ == '__main__':
main()
下面利用函数实现生产者消费者模式
import queue
import threading
import time,random
def producer():
for i in range(10):
print("producer put %d to queue"%i)
data.put(i)
time.sleep(random.randrange(1))
print("Producer is finished")
def consumer():
for i in range(10):
print("consumer get %d from queue"%data.get())
time.sleep(random.randrange(1))
print("Consumer is finished")
data=queue.Queue()
t1=threading.Thread(target=producer,name="Producer")
t2=threading.Thread(target=consumer,name="Consumer")
t1.start()
t2.start()
t1.join()
t2.join()
最后的结果
折腾了大半天总算把python的多线程编程以及生产者消费者模型搞明白o(╯□╰)o