3. 线程安全
一个进程中可以有多个线程,且线程共享所有进程中的资源。 多个线程同时去操作一个“东西”,可能会存在数据混乱的情况。 为了避免这种情况,可以使用线程锁(Lock)来保证同一时刻只有一个线程在操作共享资源。
python
import threading
lock_object = threading.RLock()
Loop = 1000000
number = 0
def _add(count):
lock_object.acquire() # 申请锁
global number
for _ in range(count):
number += 1
lock_object.release() # 释放锁
def _sub(count):
lock_object.acquire() # 申请锁
global number
for _ in range(count):
number -= 1
lock_object.release() # 释放锁
t1 = threading.Thread(target=_add, args=(Loop,))
t2 = threading.Thread(target=_sub, args=(Loop,))
t1.start()
t2.start()
t1.join()
t2.join()
print(number) # 输出结果一定是 0python
import threading
number = 0
lock_object = threading.RLock()
def task():
print("线程开始")
with lock_object: # 基于上下文管理,使用 with 语句自动申请和释放锁
global number
for _ in range(1000000):
number += 1
print(number)
for i in range(2):
t = threading.Thread(target=task)
t.start()下面列出一些在 CPython(有 GIL 的实现)中通常被视为原子/线程安全的操作:
- 列表操作:
L.append(x)、L.extend(L2)、x = L[i]、x = L.pop()、L1[i:j] = L2、L.sort()。 - 赋值/属性赋值:
x = y、x.field = y。 - 字典操作:
D[x] = y、D1.update(D2)、D.keys()(读取键视图)。
注意:这些操作在 CPython 中通常是原子的(受 GIL 影响),但在其他 Python 实现(如 PyPy、Jython)或未来解释器版本中行为可能不同。
下面是一些不是原子、存在竞态风险的示例(不应该在并发场景中直接使用,除非加锁):
python
# 非原子示例 - 不安全的读-改-写或复合操作
i = i + 1
L.append(L[-1])
L[i] = L[j]
D[x] = D[x] + 1解释:像 i = i + 1、D[x] = D[x] + 1 这类读-改-写操作由多个底层步骤组成(读取、修改、写回),在多线程同时执行时可能被其他线程中断,导致数据不一致。应使用锁(Lock/RLock)或原子结构(例如 collections.Counter 的更新要加锁)来保证正确性。
常见建议:
- 对共享可变数据(列表、字典、计数器等)进行复合操作时,务必使用锁;
- 尽量把共享数据的读操作与写操作分离,或使用线程安全的数据结构/队列(如
queue.Queue); - 在需要高并发读写且性能敏感的场景下,考虑使用进程(multiprocessing)或基于 C 扩展/第三方库的并发方案。