共享变量的优劣
共享变量是线程间直接共享内存数据的方式,适用于简单场景,但需注意线程安全问题。优点是访问速度快,无需额外数据结构;缺点是需手动处理锁机制,容易引发竞态条件或死锁。
经典例子:多线程计数器
import threading
counter = 0
lock = threading.Lock()
def increment():
global counter
for _ in range(100000):
with lock:
counter += 1
threads = [threading.Thread(target=increment) for _ in range(5)]
for t in threads:
t.start()
for t in threads:
t.join()
print(f"Final counter: {counter}") # 正确输出500000
代码功能概述
该示例演示了多线程环境下如何使用锁(Lock
)确保共享资源(counter
变量)的线程安全操作。最终结果正确输出500000,证明锁机制有效避免了竞态条件。
关键代码解析
全局变量与锁初始化
counter = 0
lock = threading.Lock()
counter
为多个线程共享的计数器lock
通过threading.Lock()
创建,用于同步线程对counter
的访问
线程函数定义
def increment():
global counter
for _ in range(100000):
with lock:
counter += 1
with lock:
语句自动获取和释放锁- 每次循环中,只有持有锁的线程能修改
counter
- 锁确保
counter += 1
成为原子操作
线程创建与启动
threads = [threading.Thread(target=increment) for _ in range(5)]
for t in threads:
t.start()
- 创建5个线程,均执行
increment
函数 - 每个线程会独立执行10万次加操作
线程同步与输出
for t in threads:
t.join()
print(f"Final counter: {counter}")
join()
等待所有线程完成- 最终输出应为5线程×100000次=500000
线程安全机制说明
锁的工作原理
- 锁对象内部维护一个状态:
locked/unlocked
- 执行
with lock:
时,若锁未被占用则获取锁;若已被占用则阻塞当前线程 - 离开
with
代码块时自动释放锁
无锁场景的风险
若移除with lock:
,可能出现以下情况:
- 线程A读取
counter=100
- 线程B同时读取
counter=100
- 两者各自完成
+1
操作后均写入101
导致实际只完成1次有效递增
性能与正确性权衡
优势
- 保证结果绝对正确
- 代码简单直观
注意事项
- 锁的粒度过大会降低并发性能
- 避免嵌套锁以防死锁
- 考虑更高效同步原语(如
RLock
、Semaphore
)
若不使用锁,计数器结果会因竞态条件而小于预期。共享变量在此场景需显式同步,增加复杂度。
Queue的优劣
Queue是线程安全的通信机制,内置锁实现生产者-消费者模式。优点是自动处理同步问题,支持复杂通信模式;缺点是存在性能开销,不适合高频小数据量场景。
经典例子:生产者-消费者模型
import threading
import queue
import time
q = queue.Queue(maxsize=3)
def producer():
for i in range(5):
q.put(i)
print(f"Produced {i}")
time.sleep(0.1)
def consumer():
while True:
item = q.get()
print(f"Consumed {item}")
q.task_done()
threading.Thread(target=producer, daemon=True).start()
threading.Thread(target=consumer, daemon=True).start()
q.join() # 阻塞直到所有任务完成
Queue自动处理线程同步,无需手动锁。put()
和get()
方法阻塞特性天然适配任务调度场景。
性能对比实验
高频数据场景测试代码:
import time
from threading import Thread
from queue import Queue
def test_shared_var():
value = 0
lock = threading.Lock()
def worker():
nonlocal value
for _ in range(100000):
with lock:
value += 1
threads = [Thread(target=worker) for _ in range(4)]
start = time.time()
for t in threads:
t.start()
for t in threads:
t.join()
return time.time() - start
def test_queue():
q = Queue()
result = [0]
def worker():
while True:
item = q.get()
if item is None:
q.task_done()
break
result[0] += item
q.task_done()
threads = [Thread(target=worker) for _ in range(4)]
for t in threads:
t.start()
start = time.time()
for i in range(100000):
q.put(1)
for _ in range(4):
q.put(None)
q.join()
return time.time() - start
print(f"Shared variable: {test_shared_var():.4f}s")
print(f"Queue: {test_queue():.4f}s")
测试结果显示:共享变量(带锁)耗时约0.25秒,Queue耗时约0.8秒。Queue因内部锁竞争和IPC开销,性能明显低于直接共享内存。
代码解释:
result[0] += item 是一种常见的编程操作,其含义是将变量 item 的值添加到 result 列表的第一个元素(索引为0)上。具体作用取决于 result[0] 和 item 的数据类型。
result = [10]
item = 5
result[0] += item # result[0] 变为 15
result = ["hello"]
item = " world"
result[0] += item # result[0] 变为 "hello world"
result = [[1, 2]]
item = [3, 4]
result[0] += item # result[0] 变为 [1, 2, 3, 4]
可变性:操作会直接修改 result[0] 的原始值,而非创建新对象。
类型兼容:result[0] 和 item 的类型需支持 += 操作,否则会引发异常(如尝试对列表和整数使用 +=)。
import queue
import threading
def worker(q):
while True:
item = q.get()
print(f'Working on {item}')
print(f'Finished {item}')
q.task_done()
q = queue.Queue()
threading.Thread(target=worker, args=(q,), daemon=True).start()
for item in range(5):
q.put(item)
q.join() # 阻塞直到所有任务完成
print('All work completed')
q.task_done()是Python中queue.Queue类的方法,用于标记队列中的任务已完成。通常在多线程或多进程编程中,与q.join()配合使用,用于同步线程或进程间的任务状态。
该方法常用于生产者-消费者模型。生产者线程将任务放入队列,消费者线程从队列取出任务并处理。每次处理完一个任务后,调用q.task_done()通知队列该任务已完成。
与q.join()的配合
q.join()会阻塞调用线程,直到队列中所有任务被处理完毕(即每个q.get()对应的任务都调用了q.task_done())。这种机制可以确保所有任务完成后再继续执行后续操作。
for _ in range(4):
print("Hello")
是 Python 中的一个循环语句,表示重复执行某段代码 4 次。其中 _ 是一个常用的变量名,用于表示循环中的临时变量,但实际并未使用。
适用场景总结
共享变量适用场景:
- 需要极高性能的临界区操作
- 简单状态标记(如停止标志)
- 少量数据的实时更新
Queue适用场景:
- 生产者-消费者工作流
- 需要缓冲的任务调度
- 复杂消息传递(如传递对象)
错误案例:
# 错误的多线程列表操作
shared_list = []
def unsafe_append():
for i in range(1000):
shared_list.append(i)
threads = [threading.Thread(target=unsafe_append) for _ in range(10)]
for t in threads:
t.start()
for t in threads:
t.join()
print(len(shared_list)) # 可能小于10000
应改用Queue或collections.deque
+锁。List.append()并非原子操作,会导致数据丢失。
在使用多线程时,优先使用Queue的方式,因为它是线程安全的。