1、介绍
多线程同时访问和操作同一资源,可能会造成数据错误。
- 脏读: 脏读最大的问题就是可能会读到不存在的数据。
- 幻读: 幻读侧重的方面是某一次的 select 操作得到的结果所表征的数据状态无法支撑后续的业务操作
- 不可重复读: 在一个事务内,最开始读到的数据和事务结束前的任意时刻读到的同一批数据出现不一致的情况。
解决办法是对资源进行限制,同一时间只允许一个线程进行访问和操作。
这里的资源,一般是指方法、函数或者代码块。即由多行代码组成,在逻辑上属于不可切分的操作。
2、线程同步
使用 Thread 对象的 Lock 和 Rlock 可以实现简单的线程同步,这两个对象都有 acquire 方法和 release 方法,分别表示加锁和释放锁。
对于那些需要每次只允许一个线程操作的数据,可以将其操作放到 acquire 和 release 方法之间。
需要注意,同一个锁对象可以在不同方法或函数中使用,会同步锁状态。另一方面,可以声明多个锁对象,互不干扰。
3、Lock类
(1)类
class Lock:
def __init__(self) -> None:
(2)初始化
lock = threading.Lock()
(3)方法
# 上锁,对python系统资源独占,当该锁释放,其他线程才可以竞争
lock.acquire()
# 释放
lock.release()
# 判断是否上锁
def locked(self) -> bool
4、使用示例
如果不是考虑效率,而是为了并发的效果。一般,会搭配time.sleep函数,使每个线程在结束核心任务后,进行等待状态,让其他线程竞争执行。
如果存在多个线程使用同一资源,需要对各线程中涉及到该资源的代码进行上锁和释放处理,线程等待需要写在释放后才有预期的效果。
```python
import threading
import time
lock = threading.Lock()
def run():
arr = []
t = threading.Thread(target=a, args=["zs", 10, arr])
t1 = threading.Thread(target=p, args=[arr])
t1.start()
t.start()
t.join()
t1.join()
# 输出结果数组
def p(result: list):
while True:
lock.acquire()
print(len(result))
lock.release()
time.sleep(4)
def a(name, count, result: list):
for i in range(count):
lock.acquire()
print("正在添加", name + ":" + str(i))
result.append(name + ":" + str(i))
lock.release()
time.sleep(3)
run()
5、使用示例2
lock锁的使用:
线程内部,只有对共享的资源操作代码进行上锁和释放锁才有意义,对非共享的资源操作代码上锁是无意义的,反而降低了多线程的效率
不同线程间,只有使用的同一个锁对象,才会实现对某共享资源的同步操作。
各线程内执行到调用lock对象的acquire()方法时,判断锁是否被释放状态。如果是释放状态,则上锁,并继续执行。如果是锁定状态,则休眠至其他线程将锁打开。
import threading
import time
class stu:
# 同步有效,交替执行
lock = threading.Lock()
def __init__(self, name):
self.name = name
self.id = 0
# 同步无效,各自执行,混乱
# self.lock = threading.Lock()
def p(self, delay):
while self.id < 30:
self.lock.acquire()
print(self.name, self.id)
self.id = self.id + 1
time.sleep(delay)
self.lock.release()
stu1 = stu("a")
t1 = threading.Thread(target=stu1.p, args=[2.5])
t1.start()
stu2 = stu("b")
t2 = threading.Thread(target=stu2.p, args=[2.5])
t2.start()