Skip to content

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)  # 输出结果一定是 0
python
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] = L2L.sort()
  • 赋值/属性赋值:x = yx.field = y
  • 字典操作:D[x] = yD1.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 + 1D[x] = D[x] + 1 这类读-改-写操作由多个底层步骤组成(读取、修改、写回),在多线程同时执行时可能被其他线程中断,导致数据不一致。应使用锁(Lock/RLock)或原子结构(例如 collections.Counter 的更新要加锁)来保证正确性。

常见建议:

  • 对共享可变数据(列表、字典、计数器等)进行复合操作时,务必使用锁;
  • 尽量把共享数据的读操作与写操作分离,或使用线程安全的数据结构/队列(如 queue.Queue);
  • 在需要高并发读写且性能敏感的场景下,考虑使用进程(multiprocessing)或基于 C 扩展/第三方库的并发方案。

构建时间:11/21/2025, 1:28:39 PM | 本博客内容均为自己学习,如内容涉及侵权,请联系邮箱:pangzl0215@163.com