学科分类
目录

互斥锁

互斥锁是最简单的加锁技术,它只有两种状态:锁定(locked)和非锁定(unlocked)。当某个线程需要更改共享数据时,它会先对共享数据上锁,将当前的资源转换为“锁定”状态,其它线程无法对被锁定的共享数据进行修改;当线程执行结束后,它会解锁共享数据,将资源转换为“非锁定”状态,以便其它线程可以对资源上锁后进行修改。在售卖车票的示例中加入互斥锁,如图1所示。

img

图1 卖火车票示例(加锁后)

从图1中可以看出,每个窗口在修改剩余票数前都会上锁,确保同一时刻只能自己访问剩余票数,一旦修改完票数之后,就对剩余票数进行解锁。

threading模块中提供了一个Lock类,通过Lock类的构造方法可以创建一个互斥锁,示例如下:

mutex_lock = threading.Lock()

Lock类中定义了两个非常重要的方法acquire()和release(),分别用于锁定和释放共享数据。acquire()方法可以设置锁定共享数据的时长,其声明如下:

acquire(blocking=Truetimeout=-1)

以上方法中,blocking参数代表是否阻塞当前线程,若设为True(默认),则会阻塞当前线程至资源处于非锁定状态;若设为False,则不会阻塞当前线程。需要注意的是,处于锁定状态的互斥锁调用acquire()方法会再次对资源上锁,处于非锁定状态的互斥锁调用release()方法会抛出RuntimeError异常,具体使用流程如图2所示。

img

图2 Lock类方法的使用

下面模拟现实中卖火车票的场景:售票厅总共有100张票,它同时开启了两个售票,每个窗口都实时显示剩余的票数。具体代码如下。

 1  from threading import Thread, Lock
 2  import threading
 3  total_ticket = 100          # 总票数
 4  def sale_ticket():          # 卖票
 5    global total_ticket
 6    while total_ticket > 0:     # 有剩余火车票
 7      mutex_lock.acquire()     # 对资源上锁,将互斥锁置为“锁定”状态
 8      if total_ticket > 0:
 9        total_ticket -= 1    # 总票数减少1张
 10       print('%s卖出一张票' % threading.currentThread().name)
 11     print('剩余票数: %d' % total_ticket)
 12     mutex_lock.release()     # 对资源解锁,将互斥锁置为“非锁定”状态
 13 if __name__ == '__main__': 
 14   mutex_lock = Lock()         # 创建互斥锁
 15   thread_one = Thread(target=sale_ticket, name='窗口1')  # 卖票窗口1
 16   thread_one.start()
 17   thread_two = Thread(target=sale_ticket, name='窗口2')  # 卖票窗口2
 18   thread_two.start()

上述代码中,第3行定义了表示总票数的全局变量total_ticket,并设置该变量的初始值为100;第4~12行定义了卖票函数sale_ticket(),在该函数中使用while语句控制重复卖票的操作,包括对资源上锁、卖一张票、提示哪个窗口卖票、显示剩余票数和对资源解锁;第14行创建了一个表示互斥锁的Lock类对象;第15~18行分别创建了两个表示“卖票窗口1”和“卖票窗口2”的两个线程thread_one和thread_two,并且调用start()方法启动两个线程执行卖票的任务。

运行代码,结果如下所示:

窗口1卖出一张票
剩余票数: 99
窗口1卖出一张票
剩余票数: 98
......
窗口2卖出一张票
剩余票数: 5
窗口2卖出一张票
剩余票数: 4
窗口2卖出一张票
剩余票数: 3
窗口1卖出一张票
剩余票数: 2
窗口1卖出一张票
剩余票数: 1
窗口1卖出一张票
剩余票数: 0
剩余票数: 0

由以上结果可知,每输出一次“窗口“卖出一张票”就显示剩余票数,且数量是依次递减的,没有出现剩余票数相同的冲突现象。

点击此处
隐藏目录