本篇文章为本人学习python并发编程的过程记录,欢迎感兴趣的同学们交流讨论!
1. 什么是线程安全?
- 线程安全指的是某个函数或某个函数库在多线程的运行环境下,能够正确处理各个线程之间的共享变量,保证程序运行的安全性。
- 线程不安全则是受到多线程程序运行的特性影响,造成相应的程序的逻辑性错误,从而产生严重的安全隐患。
2. 使用Lock解决线程的安全问题
- Lock可以在“关键的位置”提供串行特性,保证数据安全与独立
- 形象理解:我们可以将关键的方法或变量理解为一个隧道,而Lock则是在隧道门口添加的一位 “守门员” ,每一个从这里经过的线程都需要跟这位守门员打声招呼,而守门员的责任则是:当隧道中有人时让后面的人等待,当隧道中没有人时放行。
3. Lock的两种使用方法
- 定义锁
import threading
lock = threading.Lock()
- try-finally模式
lock.acquire() # 获取锁
try:
...
finally:
lock.release() # 释放锁
- with 模式
with lock:
...
更常使用的是with模式
4. 银行取钱示例
import threading
import time
# 创建一个Lock
lock = threading.Lock()
class Account: # 定义Account类
def __init__(self, balance):
self.balance = balance
def draw_0(account, money): # 不启用Lock
if account.balance > money:
time.sleep(0.1) # 通过sleep造成线程阻塞
account.balance -= money
print('取钱成功!')
else:
print('取钱失败!')
def draw_1(account, money):
with lock: # 启用Lock
if account.balance > money:
time.sleep(0.1) # 即使有sleep仍然线程安全
account.balance -= money
print('取钱成功!')
else:
print('取钱失败!')
if __name__ == '__main__':
account = Account(1000) # 开设一个账户
# 未启用线程安全的版本,由于有sleep的存在,必定造成不安全现象
# thread_0 = threading(name='thread_0', target=draw_0, args=(800,))
# thread_1 = threading(name='thread_1', target=draw_0, args=(800,))
# 线程安全版本,启用Lock
thread_0 = threading(name='thread_0', target=draw_1, args=(800,))
thread_1 = threading(name='thread_1', target=draw_1, args=(800,))
这里有一个小问题需要进行一下说明,有的同学在看了上面的例子或者没有好好理解的情况下,会认为使用Lock会将多线程打回原形,重新变为串行。需要注意的是,在上面的例子中,由于案例十分简单,实际上也确实回到了串行操作,但是我们需要知道的是,在实际的业务中,可能存在非常多不同的线程,各个线程执行这不同的工作,如网络信息传输加载等等;在这种情况下,线程安全才表现出它的重要行,可以想见,当因为Lock出现阻塞时(Lock只能够阻塞起负责的部分),并非变为了完全串行,仍然有其他的线程可以使用CPU。