0Pricing
Python Academy · 课时

安全共享数据

使用队列和锁

安全共享数据 是 CoddyKit 上的免费 Python Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Python Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Python Academy 课程共包含 4 节课。

竞态条件问题

当多个线程修改同一份数据时,它们的操作可能交错执行,从而破坏结果。这就是竞态条件。我们需要使用同步工具来安全地共享数据。

import threading

counter = 0

def bump():
    global counter
    for _ in range(100000):
        counter += 1

ts = [threading.Thread(target=bump) for _ in range(2)]
for t in ts: t.start()
for t in ts: t.join()
print('counter (may be < 200000):', counter)

Lock 解决问题

threading.Lock 确保同一时刻只有一个线程进入临界区。请在共享数据更新外层使用 with 代码块来获取它。

import threading

counter = 0
lock = threading.Lock()

def bump():
    global counter
    for _ in range(100000):
        with lock:
            counter += 1

ts = [threading.Thread(target=bump) for _ in range(2)]
for t in ts: t.start()
for t in ts: t.join()
print('counter:', counter)

acquire 和 release

可以手动使用 acquire() 和 release() 操作锁,但 with 形式更安全,因为即使发生错误也会释放锁。

import threading

lock = threading.Lock()
lock.acquire()
try:
    print('inside critical section')
finally:
    lock.release()
print('released')

线程安全的队列

queue.Queue 类本身就是线程安全的。线程可以在没有显式锁的情况下调用 put 和 get,因此队列是传递数据的首选方式。

import queue, threading

q = queue.Queue()

def producer():
    for i in range(3):
        q.put(i)

t = threading.Thread(target=producer)
t.start()
t.join()
while not q.empty():
    print('got', q.get())

生产者与消费者

经典模式是:生产者将工作 put 到队列中,消费者 get 出工作并进行处理。队列会处理所有加锁操作。

import queue, threading

q = queue.Queue()
results = []

def consumer():
    while True:
        item = q.get()
        if item is None:
            break
        results.append(item * 10)
        q.task_done()

t = threading.Thread(target=consumer)
t.start()
for i in range(3):
    q.put(i)
q.put(None)
t.join()
print(results)

join 和 task_done

q.join() 会阻塞,直到每个被 put 的项目都对应调用了 task_done()。这样主线程就可以等待所有工作完成。

import queue, threading

q = queue.Queue()

def worker():
    while True:
        item = q.get()
        q.task_done()

threading.Thread(target=worker, daemon=True).start()
for i in range(5):
    q.put(i)
q.join()
print('all items processed')

RLock 用于可重入

如果同一个线程尝试获取普通的 Lock 两次,就会发生死锁。threading.RLock(可重入锁)允许这样做,并会记录获取次数。

import threading

rlock = threading.RLock()

def outer():
    with rlock:
        inner()

def inner():
    with rlock:
        print('re-entered safely')

outer()

Event 用于发出信号

threading.Event 允许一个线程向其他线程发出信号。线程会调用 wait() 等待,直到另一个线程调用 set()。

import threading

start = threading.Event()

def worker():
    start.wait()
    print('go!')

t = threading.Thread(target=worker)
t.start()
print('setting event')
start.set()
t.join()

跨进程共享数据

跨进程共享数据时,请改用 multiprocessing.Queue。它会在独立的内存空间之间序列化对象。

import multiprocessing as mp

def producer(q):
    for i in range(3):
        q.put(i * i)

if __name__ == '__main__':
    q = mp.Queue()
    p = mp.Process(target=producer, args=(q,))
    p.start()
    p.join()
    while not q.empty():
        print('received', q.get())

避免死锁

当线程永远等待彼此持有的锁时,就会发生死锁。请确保在所有地方都按相同顺序获取多个锁,以避免死锁。

import threading

lock_a = threading.Lock()
lock_b = threading.Lock()

def safe():
    with lock_a:
        with lock_b:
            print('acquired in consistent order')

safe()

优先使用队列而不是锁

在可能的情况下,请优先使用队列进行消息传递,而不是使用锁保护共享的可变状态。这样更容易分析,也更不容易产生错误。

import queue

q = queue.Queue()
for word in ['safe', 'simple', 'clear']:
    q.put(word)
while not q.empty():
    print(q.get())

快速检查

请测试您对安全数据共享的理解。

总结

您已学会如何安全地共享数据:

  • 未受保护的共享状态会因竞态条件而损坏。
  • Lock 和 RLock 可以保护临界区。
  • queue.Queue 是线程安全的;跨进程时请使用 multiprocessing.Queue。
  • Event 可以在线程之间发出信号;保持一致的加锁顺序可以避免死锁。

下一课程:使用子进程运行外部程序。

常见问题解答

「安全共享数据」课时是免费的吗?

是的 — 「安全共享数据」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Python Academy 课程的其余内容,请升级到 CoddyKit PRO。 Python Academy 课程共包含 4 节课。

「安全共享数据」这节课中我会学到什么?

使用队列和锁 你通过在浏览器中直接运行的动手代码来练习 Python Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Python Academy 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Python Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。

「安全共享数据」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Python Academy 课中编写并运行代码吗?

能。每节 Python Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 线程与 GIL
  2. ThreadPoolExecutor
  3. multiprocessing 基础
  4. 安全共享数据
← 返回 Python Academy