ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

python | 如何用Python锁避免并发错误?

2026/9/4 6:30:13 拓冰建站 浏览量
python | 如何用Python锁避免并发错误? 原文链接如何用锁避免并发错误对于并发编程而言, 多线程属于提升程序执行效率的一种有效办法, 特别是在面对需要处理密集型任务的情况下而言, 其存在的多线程能够彰显突出地减少程序所需要的执行时间在并发编程内。不过然, 多个线程并发执行也造成了数据一致性相关问题产生, 特别是当多个线程同时进行访问或者修改共享资源之时, 极易出现“竞态条件”Race, 进而致使出现不可预测的错误。为了实现规避这些问题的结果而言, 锁Lock成为解决并发访问的关键工具。什么是锁锁属于一种确保多个线程访问共享资源得以有序进行的同步机制, 它能够在一个线程着手访问共享资源之际, 对其他线程同时访问此资源予以有效阻止, 直至该线程成功释放锁, 别的线程才能够接着开展访问, 借由这种方式, 锁能够保证同一时刻仅有一个线程针对共享资源展开操作, 进而有效规避竞态条件。在其中, 锁是经模块予以提供的, 最为常见的锁对象包含Lock以及RLock, Lock属于简单的一次性锁, RLock乃重入锁, 它准许同一个线程多次获取锁。中锁的使用场景首先, 对于共享资源的修改, 存在这样一种状况, 当多个线程需要同时去进行对一个共享资源的修改操作时, 要是缺乏锁的保护, 那么就极有可能会致使数据不一致的情况发生。对于读写操作的协调而言, 于某些场景之时, 会出现这样一种状况, 即有一个线程, 它可能正处于对资源进行修改的过程当中, 然而与此同时, 另外还有一个线程, 该线程尝试去读取这些资源, 在如此这般的情形之下, 锁能够保证数据的正确性。躲开竞态条件, 当多个线程争抢同一个资源之际, 锁能够协调各个线程的执行次序, 从而躲开竞态条件。基本锁的用法创建锁对象通过.Lock()创建一个锁对象。获取锁, 是借助调用lock.()方法达成该目的。要是锁已被别的线程占据, 那么当前线程会处于阻塞状况, 直至锁被释放为止。把所拥有的锁释放掉: 在那种情况下, 也就是线程于后来不再有对共享资源进行访问的需求之际, 能够去调用lock.()这个操作实现锁的释放, 如此一来, 其他遭受阻塞的线程便有机会继续去获取该锁了。多个线程修改共享变量以下示例呈现出了多个线程对于共享变量进行修改的情况当中, 要是没有采用锁的话, 相应地就会致使竞态条件的出现。import threading # 共享资源 counter 0 def increment(): global counter for _ in range(100000): counter 1 # 创建两个线程 thread1 threading.Thread(targetincrement) thread2 threading.Thread(targetincrement) # 启动线程 thread1.start() thread2.start() # 等待线程完成 thread1.join() thread2.join() print(f没有锁的情况下计数器的最终值为: {counter})在那上面的代码里头, 开启了两个线程一同去递增变量, 鉴于众多线程同时对这个变量予以访问以及修改, 故而最终导致的结果有可能是不正确的。解决方案使用锁可以通过引入锁来解决上面的竞态条件问题。修改后的代码如下import threading # 共享资源 counter 0 lock threading.Lock() def increment(): global counter for _ in range(100000): # 获取锁 with lock: counter 1 # 创建两个线程 thread1 threading.Thread(targetincrement) thread2 threading.Thread(targetincrement) # 启动线程 thread1.start() thread2.start() # 等待线程完成 thread1.join() thread2.join() print(f使用锁的情况下计数器的最终值为: {counter})在这个示例情形当中, 于开展某项操作之际运用了with lock, 以此保证每一回仅有一个线程能够去修改变量形态以确保避免出现竞态条件结果, 终于, 计数器的所拥有的值将会是准确无误的。重入锁RLock允许同一个线程, 在持有锁的时候, 再次获得该锁, 且不会发生死锁的, 一种特殊的锁, 是重入锁RLock, 要是在同一线程里, 需多次获取锁, 那么就能够使用RLock。import threading lock threading.RLock() def recursive_function(n): if n 0: lock.acquire() print(f锁已获取: {n}) recursive_function(n - 1) lock.release() # 启动线程执行递归函数 thread threading.Thread(targetrecursive_function, args(5,)) thread.start() thread.join()在这样的一个示例当中, 借助RLock达成了递归函数的锁定以及解锁的相关操作, 同一线程能够多次去获取并且释放锁, 进而不会出现死锁的情况。锁的非阻塞模式以下是改写内容: 在一些情形之下, 或许并不愿线程因获取锁这件事而被阻塞住。该时候, 能够运用锁的非阻塞模式。借着传递block等于False这个参数, lock.()该方法会马上返回False, 并非阻塞线程。import threading import time lock threading.Lock() def task(): if lock.acquire(blockingFalse): print(锁已获取执行任务) time.sleep(1) lock.release() else: print(无法获取锁跳过任务) # 启动两个线程尝试获取锁 thread1 threading.Thread(targettask) thread2 threading.Thread(targettask) thread1.start() thread2.start() thread1.join() thread2.join()于此示例之内, 有两个线程一块儿尝试着去获取锁, 然而鉴于该锁属于非阻塞模式, 所以当第二个线程没办法获取到锁的时候, 它便会直接跨越任务, 而并非会被阻塞住。读写锁另外有一种锁, 它并非普通的锁, 而是一种更为高级的锁, 也就是读写锁, 读写锁能够让多个线程同时去读取共享资源, 然而在进行写入操作的时候, 一定要保证只有一个线程在执行写入动作, 其余的线程都会被阻塞。标准库并没有内置读写锁可以通过第三方库来实现。安装库pip install readerwriterlock使用读写锁from readerwriterlock import rwlock import threading import time # 创建读写锁 lock rwlock.RWLockFair() # 共享资源 data [] def reader(): with lock.gen_rlock(): print(f{threading.current_thread().name} 正在读取数据: {data}) time.sleep(1) def writer(): with lock.gen_wlock(): data.append(1) print(f{threading.current_thread().name} 正在写入数据) time.sleep(1) # 创建多个读线程和写线程 threads [] for _ in range(3): t threading.Thread(targetreader) threads.append(t) for _ in range(2): t threading.Thread(targetwriter) threads.append(t) # 启动所有线程 for t in threads: t.start() # 等待所有线程完成 for t in threads: t.join()在于此例当中, 读写锁达成了多个线程能够同时去读取数据, 然而写操作却是独占性质的。要是有一个线程正在开展写入数据操作, 那么所有其他线程, 不管是读线程亦或是写线程, 都会处在被阻塞状态, 一直到写操作完结。死锁与避免使用锁之际, 需格外留意的一个问题, 便是死锁。死锁出现于多个线程皆在等候彼此释放锁的情形下, 致使程序陷入无限等待的状态。为了避免死锁可以采取以下策略避免嵌套锁定尽量减少线程同时持有多个锁的情况。使用超时机制为锁设置超时时间确保线程不会无限期等待。遵循锁的顺序确保多个线程获取锁的顺序一致避免交叉等待。超时机制lock threading.Lock() def task(): if lock.acquire(timeout2): # 尝试获取锁最多等待2秒 print(获取到锁) time.sleep(3) lock.release() else: print(无法获取到锁任务超时) thread1 threading.Thread(targettask) thread2 threading.Thread(targettask) thread1.start() thread2.start() thread1.join() thread2.join()在这个示例里头, 第二个线程于等待2秒之后没能获取到锁, 所以任务超时后退出。总结锁是在多线程编程里, 用于确保数据一致性以及避免竞态条件的关键工具。于多线程的环境当中, 多个线程有同时去访问跟修改共享资源的可能, 进而引发数据冲突。借助使用锁的方式, 开发者能够确保在同一时刻, 仅仅有一个线程进行共享资源的访问, 以此来避免并发方面的问题。本文阐述了其中常见的锁机制, 包含Lock、RLock、读写锁等, 还详细地解释了它们的使用场景以及应用方法。除此之外, 还展示了怎样通过锁的非阻塞模式以及超时机制去避免死锁问题。做到对这些锁的用法予以掌握, 那么开发者便能够切实有效地让多线程程序的安全性以及稳定性得以提高。