说明
《Python 教程》 持续更新中,提供建议、纠错、催更等加作者微信: gairuo123(备注:pandas教程)和关注公众号「盖若」ID: gairuo。跟作者学习,请进入 Python学习课程。欢迎关注作者出版的书籍:《深入浅出Pandas》 和 《Python之光》。
在 Python 的 threading 模块中,线程锁(Lock)用于实现线程之间的同步,确保在同一时刻只有一个线程能够访问被锁保护的代码区域。线程锁(Lock)对象用于在多线程环境中控制对共享资源的访问,以避免数据竞争和不一致的问题。
线程锁通过在一个线程访问共享资源时阻止其他线程访问,来确保资源的完整性。只有获取锁的线程才能访问共享资源,其他线程必须等待锁被释放。当一个线程获取到锁后,其他线程如果尝试获取该锁,会被阻塞,直到持有锁的线程释放锁。这样可以避免多个线程同时对共享资源进行修改,导致数据不一致或出现错误的结果。
简单说,线程锁的核心原理是保证在同一时间只有一个线程可以获得锁并访问被保护的资源。当一个线程获得锁时,其他尝试获取同一个锁的线程将被阻塞,直到持有锁的线程释放它。这种机制确保了共享资源的互斥访问,防止了数据竞争和不一致性。
方法很简单:
import threading
lock = threading.Lock()
常用方法:
acquire(blocking=True, timeout=-1)
: 尝试获取锁。如果 blocking 为 True(默认值),则线程会阻塞直到获取到锁;如果设置为 False,则不会阻塞,如果无法立即获取锁会返回 False。timeout 用于指定阻塞的超时时间(以秒为单位),超过时间未获取到锁则返回 False。release()
: 释放锁。locked()
: 如果锁被某个线程持有,返回 True原始锁是一个在锁定时不属于特定线程的同步基元组件。在Python中,它是能用的最低级的同步基元组件,由 _thread 扩展模块直接实现。
原始锁处于 "锁定" 或者 "非锁定" 两种状态之一。它被创建时为非锁定状态。它有两个基本方法, acquire() 和 release() 。当状态为非锁定时, acquire() 将状态改为 锁定 并立即返回。当状态是锁定时, acquire() 将阻塞至其他线程调用 release() 将其改为非锁定状态,然后 acquire() 调用重置其为锁定状态并返回。 release() 只在锁定状态下调用; 它将状态改为非锁定并立即返回。如果尝试释放一个非锁定的锁,则会引发 RuntimeError 异常。
锁同样支持 上下文管理协议。
当多个线程在 acquire() 等待状态转变为未锁定被阻塞,然后 release() 重置状态为未锁定时,只有一个线程能继续执行;至于哪个等待线程继续执行没有定义,并且会根据实现而不同。
所有方法的执行都是原子性的。
当多个线程需要访问和修改共享资源(例如一个全局变量或共享的数据结构)时,为了避免数据竞争和不一致的情况,就需要使用线程锁来保证同一时刻只有一个线程能够进行修改操作。
使用场景:
示例一:
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(1000):
lock.acquire()
try:
counter += 1
finally:
lock.release()
threads = [threading.Thread(target=increment) for _ in range(10)]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print(f"Final counter value: {counter}")
示例二:
import threading
import time
counter = 0
lock = threading.Lock()
def worker():
global counter
with lock:
current = counter
time.sleep(0.1) # 模拟一些处理时间
counter = current + 1
threads = []
for _ in range(10):
thread = threading.Thread(target=worker)
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print(f"Final counter value: {counter}")
用三个线程将数字 1-9 写入一个 txt 文档,如果不用锁则顺序是乱的:
import threading
# 创建一个锁
lock = threading.Lock()
# 当前应该写入的数字
current_number = 1
def write_to_file(number):
"""将给定的数字写入文件"""
with open('numbers.txt', 'a') as file:
file.write(f"{number}\n")
print(f"线程 {threading.current_thread().name} 写入: {number}")
def write_numbers(sart, stop):
"""尝试写入指定范围内的数字"""
global current_number
for number in range(sart, stop+1):
write_to_file(number)
# 创建文件(如果已存在则清空内容)
with open('numbers.txt', 'w') as file:
pass
# 创建3个线程
thread1 = threading.Thread(target=write_numbers, args=(1, 3), name="Thread-1")
thread2 = threading.Thread(target=write_numbers, args=(4, 6), name="Thread-2")
thread3 = threading.Thread(target=write_numbers, args=(7, 9), name="Thread-3")
# 启动线程
thread1.start()
thread2.start()
thread3.start()
# 等待所有线程完成
thread1.join()
thread2.join()
thread3.join()
print("所有数字已写入 numbers.txt")
使用锁则是正常的顺序:
import threading
# 创建一个锁
lock = threading.Lock()
# 当前应该写入的数字
current_number = 1
def write_to_file(number):
"""将给定的数字写入文件"""
with open('numbers.txt', 'a') as file:
file.write(f"{number}\n")
print(f"线程 {threading.current_thread().name} 写入: {number}")
def write_numbers(sart, stop):
"""尝试写入指定范围内的数字"""
global current_number
lock.acquire()
for number in range(sart, stop+1):
write_to_file(number)
lock.release()
# 创建文件(如果已存在则清空内容)
with open('numbers.txt', 'w') as file:
pass
# 创建3个线程
thread1 = threading.Thread(target=write_numbers, args=(1, 3), name="Thread-1")
thread2 = threading.Thread(target=write_numbers, args=(4, 6), name="Thread-2")
thread3 = threading.Thread(target=write_numbers, args=(7, 9), name="Thread-3")
# 启动线程
thread1.start()
thread2.start()
thread3.start()
# 等待所有线程完成
thread1.join()
thread2.join()
thread3.join()
print("所有数字已写入 numbers.txt")
使用 with 上下文对象:
import threading
# 创建一个锁
lock = threading.Lock()
# 当前应该写入的数字
current_number = 1
def write_to_file(number):
"""将给定的数字写入文件"""
with open('numbers.txt', 'a') as file:
file.write(f"{number}\n")
print(f"线程 {threading.current_thread().name} 写入: {number}")
def write_numbers(sart, stop):
"""尝试写入指定范围内的数字"""
global current_number
with lock:
for number in range(sart, stop+1):
write_to_file(number)
# 创建文件(如果已存在则清空内容)
with open('numbers.txt', 'w') as file:
pass
# 创建3个线程
thread1 = threading.Thread(target=write_numbers, args=(1, 3), name="Thread-1")
thread2 = threading.Thread(target=write_numbers, args=(4, 6), name="Thread-2")
thread3 = threading.Thread(target=write_numbers, args=(7, 9), name="Thread-3")
# 启动线程
thread1.start()
thread2.start()
thread3.start()
# 等待所有线程完成
thread1.join()
thread2.join()
thread3.join()
print("所有数字已写入 numbers.txt")
假设有一个银行账户类,多个线程同时进行存款和取款操作。
import threading
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
self.lock = threading.Lock()
def deposit(self, amount):
self.lock.acquire()
self.balance += amount
print(f"存款: {amount}, 新余额: {self.balance}")
self.lock.release()
def withdraw(self, amount):
self.lock.acquire()
if self.balance >= amount:
self.balance -= amount
print(f"取款: {amount}, 新余额: {self.balance}")
else:
print("余额不足,取款失败")
self.lock.release()
account = BankAccount(1000)
threads = [
threading.Thread(target=account.deposit, args=(500,)),
threading.Thread(target=account.withdraw, args=(200,)),
threading.Thread(target=account.deposit, args=(300,))
]
for thread in threads:
thread.start()
for thread in threads:
thread.join()
print("最终余额:", account.balance)
在上述案例中,通过线程锁保证了存款和取款操作的原子性,避免了并发访问导致的余额错误。这个例子很好地说明了线程锁在实际应用中的重要性。在没有锁的情况下,并发的存款和取款操作可能会导致余额计算错误。通过使用锁,我们确保了每个操作的原子性,维护了数据的一致性。
更加复杂点:
import threading
import time
import random
class BankAccount:
def __init__(self, balance=0):
self.balance = balance
self.lock = threading.Lock()
def deposit(self, amount):
with self.lock:
current_balance = self.balance
time.sleep(0.1) # 模拟处理时间
self.balance = current_balance + amount
return self.balance
def withdraw(self, amount):
with self.lock:
if self.balance >= amount:
current_balance = self.balance
time.sleep(0.1) # 模拟处理时间
self.balance = current_balance - amount
return amount
else:
return 0
def get_balance(self):
with self.lock:
return self.balance
def customer(name, account):
for _ in range(5):
action = random.choice(["deposit", "withdraw"])
amount = random.randint(10, 100)
if action == "deposit":
balance = account.deposit(amount)
print(f"{name} deposited {amount}. New balance: {balance}")
else:
withdrawn = account.withdraw(amount)
if withdrawn:
print(f"{name} withdrew {withdrawn}. New balance: {account.get_balance()}")
else:
print(f"{name} failed to withdraw {amount}. Balance: {account.get_balance()}")
time.sleep(random.random())
def main():
account = BankAccount(500)
customers = ["Alice", "Bob", "Charlie", "David", "Eve"]
threads = []
for name in customers:
thread = threading.Thread(target=customer, args=(name, account))
threads.append(thread)
thread.start()
for thread in threads:
thread.join()
print(f"Final account balance: {account.get_balance()}")
if __name__ == "__main__":
main()
这种模式适用于多种需要保护共享资源的场景,如数据库连接池、缓存系统、日志记录器等。正确使用线程锁可以帮助构建健壮、可靠的多线程应用程序。
更新时间:July 2, 2024, 9:48 a.m. 标签:python threading 线程 线程锁