Python多线程Lock锁机制详解:从基础使用到死锁预防
本文深入解析Python中threading.Lock的使用方式。通过对比未加锁与加锁的代码示例,展示线程执行顺序的控制方法。详细阐述线程不安全的原因,介绍Lock类的工作原理,并探讨RLock解决死锁问题的机制,帮助开发者掌握多线程编程中的资源同步技巧。
Python多线程中Lock锁(threading.Lock)的具体应用方法
核心疑问
多线程环境下任务默认并发执行,若需强制实现线程A先于线程B执行,应如何处理?
解决方案
引入Python的多线程锁机制(Lock)。
代码示例
未使用多线程锁Lock的情况:
def a():
for i in range(3):
print('a%d' % (i + 1))
time.sleep(1)
def b():
for i in range(3):
print('b%d' % (i + 1))
time.sleep(1)
T = threading.Thread(target=a)
T.start()
T = threading.Thread(target=b)
T.start()运行结果分析:可见线程A与线程B交替执行,并未按顺序完成。
a1 b1 a2b2
a3 b3
Process finished with exit code 0
使用多线程锁Lock后的情况:
lock = threading.Lock()
def a():
lock.acquire()
for i in range(3):
print('a%d' % (i + 1))
time.sleep(1)
lock.release()
def b():
lock.acquire()
for i in range(3):
print('b%d' % (i + 1))
time.sleep(1)
lock.release()
T = threading.Thread(target=a)
T.start()
T = threading.Thread(target=b)
T.start()运行结果分析:可见线程A完整执行完毕后,线程B才开始执行。
a1 a2 a3 b1 b2 b3
Process finished with exit code 0
Python多线程中锁的核心概念
锁对象可独立定义与提取
mutex = threading.Lock() #锁的使用 #创建锁 mutex = threading.Lock() #锁定 mutex.acquire([timeout]) #释放 mutex.release()
概念解析
关于资源加锁的常见误解:并非直接给资源加锁,而是通过锁对象来锁定资源。开发者可以定义多个锁对象,当需要独占某一资源时,任意一个锁均可用于锁定该资源。
这类似于使用不同类型的钥匙都能打开同一把门锁的原理。
import threading
import time
counter = 0
counter_lock = threading.Lock() #只是定义一个锁,并不是给资源加锁,你可以定义多个锁,像下两行代码,当你需要占用这个资源时,任何一个锁都可以锁这个资源
counter_lock2 = threading.Lock()
counter_lock3 = threading.Lock()
#可以使用上边三个锁的任何一个来锁定资源
class MyThread(threading.Thread):#使用类定义thread,继承threading.Thread
def __init__(self,name):
threading.Thread.__init__(self)
self.name = "Thread-" + str(name)
def run(self): #run函数必须实现
global counter,counter_lock #多线程是共享资源的,使用全局变量
time.sleep(1);
if counter_lock.acquire(): #当需要独占counter资源时,必须先锁定,这个锁可以是任意的一个锁,可以使用上边定义的3个锁中的任意一个
counter += 1
print "I am %s, set counter:%s" % (self.name,counter)
counter_lock.release() #使用完counter资源必须要将这个锁打开,让其他线程使用
if __name__ == "__main__":
for i in xrange(1,101):
my_thread = MyThread(i)
my_thread.start()线程不安全现象
考察一个基础的多线程示例。代码创建了一个继承自Thread类的子类MyThread作为线程启动类,并重写了run方法,线程启动后自动调用该方法。示例中创建了10个线程并加入列表,通过for循环依次启动各线程,最后再次使用for循环调用join方法,确保所有线程结束后主线程才退出。
该代码表面简单,实则隐含严重问题。虽然逻辑上按顺序创建了10个线程并期望每个线程为变量n增加1,但实际执行中,可能是线程A执行n++后,线程C执行n++,接着线程B执行n++。
这涉及“锁”的概念。若多个线程同时操作同一对象且缺乏保护,会导致程序结果不可预期(例如在run方法中加入time.sleep(1)并输出线程名,会发现输出杂乱无章,因为print语句可能只输出一半字符时线程被挂起,转而执行其他线程)。这种现象被称为“线程不安全”。
线程锁机制
Threading模块提供了Threading.Lock类作为解决方案。通过创建该对象,在线程函数执行前“抢占”该锁,执行完成后“释放”该锁,可确保同一时刻仅有一个线程持有该锁。此时对公共对象进行操作,即可避免线程不安全现象。
修改后的代码如下:
# coding : uft-8 __author__ = 'Phtih0n' import threading, time class MyThread(threading.Thread): def __init__(self): threading.Thread.__init__(self) def run(self): global n, lock time.sleep(1) if lock.acquire(): print n , self.name n += 1 lock.release() if "__main__" == __name__: n = 1 ThreadList = [] lock = threading.Lock() for i in range(1, 200): t = MyThread() ThreadList.append(t) for t in ThreadList: t.start() for t in ThreadList: t.join()
1 Thread-2 2 Thread-3 3 Thread-4 4 Thread-6 5 Thread-7 6 Thread-1 7 Thread-8 8 Thread-9 9 Thread-5 Process finished with exit code 0
代码首先建立了threading.Lock类对象lock。在run方法中,使用lock.acquire()获取锁。此时其他线程无法获得该锁,将阻塞在“if lock.acquire()”处,直到当前线程通过lock.release()释放锁。
因此,if语句包裹的代码块成为完整执行单元,不再出现执行一半被挂起去执行其他线程的情况,最终结果整齐有序。
这类似于Java中使用synchronized关键字修饰方法,目的相同:确保某段代码被一个线程执行时,不会中断并跳转到另一个线程。
上述情况适用于多线程占用单个公共对象。若多个线程调用多个对象,且线程A通过A锁占用对象A,线程B通过B锁占用对象B,而线程A试图调用对象B、线程B试图调用对象A,双方将无限等待,导致“死锁”。
Threading模块提供了RLock类,即可重入锁。该锁内部维护一个Lock对象和一个counter计数器。counter记录acquire的次数,允许资源被多次请求。只有当所有RLock被release后,其他线程才能获取资源。在同一个线程中,RLock.acquire可被多次调用,利用此特性可解决部分死锁问题。
以上为个人经验分享,旨在提供参考,感谢大家的支持。
本站声明
本文内容仅供参考,不代表本站观点。转载或引用请注明出处。如发现内容问题,请通过联系我们页面反馈。













