加锁,在并发编程里,那可太重要了!不然,数据乱套了,那可就麻烦大了。Python 里,加锁其实挺方便的,但门道也不少,一不小心,死锁了,那真是欲哭无泪。

最常用的,肯定是 threading.Lock 了。创建一个 Lock 对象,然后用 acquire() 获取锁,用 release() 释放锁。就这么简单吗?当然没这么简单!

“`python
import threading

lock = threading.Lock()

def my_function():
lock.acquire()
try:
# 这里是需要保护的代码
print(“线程获取了锁”)
finally:
lock.release()

创建并启动线程

thread1 = threading.Thread(target=my_function)
thread2 = threading.Thread(target=my_function)

thread1.start()
thread2.start()
“`

这段代码,两个线程会竞争同一个锁,保证了 my_function 里面的代码同一时间只有一个线程在执行。但是,你看那个 finally 没?一定要有!不然,万一 try 里面的代码出错了,锁没释放,那就死锁了!血的教训啊!

with 语句,强烈推荐使用!它能自动帮你 acquire()release() 锁,代码更简洁,也更安全。

“`python
import threading

lock = threading.Lock()

def my_function():
with lock:
# 这里是需要保护的代码
print(“线程获取了锁(使用 with 语句)”)

创建并启动线程

thread1 = threading.Thread(target=my_function)
thread2 = threading.Thread(target=my_function)

thread1.start()
thread2.start()
“`

用了 with,就再也不用担心忘记释放锁了,代码也看着更舒服。简直是神器!

除了 Lock,Python 还有 RLock,也就是可重入锁。啥叫可重入?就是同一个线程可以多次 acquire() 同一个 RLock,但是也要 release() 相应次数才能真正释放锁。

“`python
import threading

lock = threading.RLock()

def recursive_function(n):
with lock:
if n > 0:
print(f”线程递归调用,n = {n}”)
recursive_function(n – 1)

创建并启动线程

thread = threading.Thread(target=recursive_function, args=(3,))
thread.start()
“`

如果不用 RLock,用普通的 Lock,那这段代码直接就死锁了,因为同一个线程试图再次 acquire() 已经持有的锁。所以,递归调用,或者在一个线程里多次需要获取同一个锁,那必须用 RLock

再说说 Condition,这个就更高级了,它基于 Lock,提供了 wait()notify() 方法,可以实现线程之间的等待和通知。简单来说,就是线程可以等待某个条件成立,然后被其他线程唤醒。

“`python
import threading
import time

condition = threading.Condition()
items = []

def producer():
with condition:
for i in range(5):
time.sleep(1) # 模拟生产过程
items.append(i)
print(f”生产者添加了物品 {i}”)
condition.notify() # 通知消费者

def consumer():
with condition:
while True:
if not items:
print(“消费者等待物品…”)
condition.wait() # 等待生产者通知
item = items.pop(0)
print(f”消费者消费了物品 {item}”)

创建并启动线程

producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)

producer_thread.start()
consumer_thread.start()
“`

这段代码,生产者生产物品,消费者消费物品。如果物品为空,消费者就等待,直到生产者生产了物品并通知它。这在多线程编程里非常常见,比如消息队列、生产者消费者模型等等。

还有 Semaphore,信号量。它维护一个计数器,acquire() 会让计数器减 1,release() 会让计数器加 1。计数器为 0 时,acquire() 会阻塞。这可以用来限制同时访问某个资源的线程数量。

“`python
import threading
import time

semaphore = threading.Semaphore(2) # 允许最多两个线程同时访问

def my_function():
with semaphore:
print(f”线程 {threading.current_thread().name} 获取了信号量”)
time.sleep(2) # 模拟访问资源
print(f”线程 {threading.current_thread().name} 释放了信号量”)

创建并启动线程

threads = []
for i in range(5):
thread = threading.Thread(target=my_function, name=f”Thread-{i}”)
threads.append(thread)
thread.start()

for thread in threads:
thread.join()
“`

这个例子里,最多只有两个线程能同时执行 my_function 里面的代码。其他线程必须等待,直到有线程释放了信号量。

说到死锁,那是并发编程里最头疼的问题之一。简单来说,就是两个或多个线程互相等待对方释放锁,导致所有线程都无法继续执行。避免死锁的方法有很多,比如:

  • 避免循环等待: 线程按照固定的顺序获取锁。
  • 设置超时时间acquire() 可以设置超时时间,如果超时了还没获取到锁,就放弃。
  • 使用死锁检测工具: 一些工具可以自动检测死锁。

其实,最关键的还是设计好你的代码,避免不必要的锁竞争。锁用得越多,死锁的风险就越高。能不用锁解决问题,尽量不用锁。

Python 的 multiprocessing 模块也提供了锁,但是它用于进程间的同步,而不是线程间的同步。进程间的锁,其实是操作系统级别的锁,比线程间的锁开销更大,但是更安全,因为进程之间是独立的,一个进程崩溃了,不会影响其他进程。

总而言之,Python 加锁是个很有意思的话题,但是一定要小心谨慎。理解各种锁的特性,避免死锁,才能写出高效、稳定的并发程序。多写代码,多踩坑,才能真正掌握加锁的精髓。毕竟,纸上得来终觉浅,绝知此事要躬行嘛!而且,并发编程这玩意儿,不亲手试试,永远体会不到那种“玄学”的滋味儿!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。