上一节记录了多线程技术以及Python多线程的的简单上手.毫无疑问,多线程是为了充分利用硬件资源尤其是CPU资源来提高任务处理效率的技术。将任务拆分为多个线程同时运行,那么属于同一个任务的多个线程之间必然会有交互和同步以便互相协作完成任务。
3. 线程同步技术
使用线程同步技术有两个原因:
- 数据安全问题,多个线程去取同一个数据源中的数据,如果不加同步锁会导致数据的脏读问题。
- 协作顺序问题,多个线程完成同一个任务时,线程之间应该有同步和交互来协调各个线程。
Python的threading模块提供了多种用于线程同步的对象,在后面会一一介绍。
3.1 Lock/RLock对象
Lock是比较低级的同步原语,当被锁定以后不属于特定的线程。一个锁有两种状态:locked 和 unlocked。
- 如果锁处于unlocked状态,
acquire()
方法将其修改为locked并立即返回 - 如果锁已处于locked状态,则阻塞当前线程并等待其他线程释放锁,然在获取到了锁后将其修改为locked并立即返回。
release()
方法用来将锁的状态由locked修改为unlocked并立即返回,如果锁状态本就是unlocked,调用该方法会抛出异常.
可重入锁RLock对象也是一种常用的线程同步原语,可以被同一个线程acquire()
多次。
- 当处于locked状态时,某线程拥有该锁
- 当处于unlocked状态时,该锁不属于任何线程。
RLock对象的acquire()/release()
调用时可以嵌套,仅当最后一个或最外层的release()
执行结束,锁才会被设置为unlocked状态
使用Lock/RLock对象实现线程同步。
import threading
import time
class MyThread(threading.Thread):
def __init__(self):
threading.Thread.__init__(self)
def run(self):
global x
lock.acquire() # 如果没有这一句,结果会重叠
for i in range(3):
x += i
time.sleep(2)
print(x)
lock.release()
lock = threading.RLock() # lock =threading.Lock()
t1 = []
for i in range(10):
t = MyThread()
t1.append(t)
x = 0
for i in t1:
i.start()
3
6
9
12
15
18
21
24
27
30
3.2 Condition对象
使用Condition对象可以在某些事件触发后才处理数据,可以用于不同线程之间的通信或通知,以实现更高级的同步。Condition对象除了具有acquire()和release()
方法之外,还有wait(),notify(),notify_all()等方法
。下面通过经典生产者-消费者问题来演示Condition对象的用法。
使用Condition对象实现线程同步。
- 首先实现生产者线程类:
class Producer(threading.Thread):
"""
生产者线程类
"""
def __init__(self, threadname):
threading.Thread.__init__(self, name=threadname)
def run(self):
global x
con.acquire()
if x == 20:
con.wait() # 如果生产了20个产品,当前生产线程就会等待消费者消费
else:
print('\nProducer:', end='')
for i in range(20):
print(x, end=' ')
x += 1
print(x)
con.notify() # 生产结束,释放锁,提醒消费者进行消费
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() # 如果商品数量是0,当前消费者线程就会等待生产者线程生产产品后提醒自己
else:
print('\nConsumer:', end='&