下面是详细的Python多线程编程(四):使用Lock互斥锁攻略。
什么是互斥锁
在多线程编程过程中,如果多个线程同时对同一资源进行读写或修改,就会出现数据竞争(Data Race)的情况。这时需要一个机制,让某个线程独占这个资源,其他线程必须等待独占线程释放该资源后才能进行读写或修改操作。这种机制就是互斥锁。
互斥锁(Mutex)是一种常见的同步原语。它可以保证在同一时刻只有一个线程可以执行特定的代码段,从而保证对竞争资源的并发访问得到正确的处理。
使用Lock互斥锁的基本方法
Python提供了Lock类来实现互斥锁的功能,引入Lock类需要使用如下语句:
import threading
lock = threading.Lock()
其中,lock对象代表了一个互斥锁,线程需要调用lock方法获取互斥锁,调用release方法释放互斥锁。
加锁和解锁的方法如下:
lock.acquire()
# 临界区代码
lock.release()
其中,acquire方法获取锁,如果锁已经被其他线程获取,则该方法会阻塞当前线程,直到锁被释放为止。release方法释放锁,如果当前线程没有获取锁,则该方法会抛出RuntimeError异常。
下面通过几个示例来演示如何使用Lock互斥锁:
示例一
下面是两个子线程并发执行的例子:
import threading
import time
def run(name):
for i in range(3):
print(name, i)
time.sleep(1)
t1 = threading.Thread(target=run, args=("Thread 1",))
t2 = threading.Thread(target=run, args=("Thread 2",))
t1.start()
t2.start()
t1.join()
t2.join()
print("All done")
当子线程并发执行的时候,输出结果可能会错乱。这是因为多个线程同时访问print函数,而print函数并不是线程安全的。
为了解决这个问题,我们可以使用Lock互斥锁,让某个线程独占print函数。修改代码如下:
import threading
import time
lock = threading.Lock()
def run(name):
for i in range(3):
lock.acquire()
print(name, i)
lock.release()
time.sleep(1)
t1 = threading.Thread(target=run, args=("Thread 1",))
t2 = threading.Thread(target=run, args=("Thread 2",))
t1.start()
t2.start()
t1.join()
t2.join()
print("All done")
通过加锁和解锁的方式,我们保证了每个线程独占print函数,输出结果不再错乱。
示例二
下面是一个计数器的例子,它包含一个全局变量count和一个类Counter:
import threading
import time
count = 0
class Counter(object):
def add(self):
global count
count += 1
def run(counter):
for i in range(100000):
counter.add()
def main():
counter = Counter()
t1 = threading.Thread(target=run, args=(counter,))
t2 = threading.Thread(target=run, args=(counter,))
t1.start()
t2.start()
t1.join()
t2.join()
print("Count is", count)
if __name__ == '__main__':
main()
当多个线程并发执行的时候,每个线程调用Counter的add方法来增加计数器的值,但是结果会出现错误。这是因为count变量是一个全局变量,并不是线程安全的,多线程并发访问会出现数据竞争的情况。
为了解决这个问题,我们可以使用Lock互斥锁来保护全局变量count,确保每个线程并发访问时不会出错。修改代码如下:
import threading
import time
count = 0
lock = threading.Lock()
class Counter(object):
def add(self):
global count
lock.acquire()
count += 1
lock.release()
def run(counter):
for i in range(100000):
counter.add()
def main():
counter = Counter()
t1 = threading.Thread(target=run, args=(counter,))
t2 = threading.Thread(target=run, args=(counter,))
t1.start()
t2.start()
t1.join()
t2.join()
print("Count is", count)
if __name__ == '__main__':
main()
通过加锁和解锁的方式,我们保证了每个线程访问全局变量count的时候都是独占的,不会出现数据竞争,从而保证了计数器的正确性。
总结
本文介绍了Python中使用Lock互斥锁的方法,并通过示例演示了如何使用Lock来解决多线程并发访问同一资源的问题。在多线程编程过程中,使用互斥锁是一种保证程序安全性的好方法。