python多线程通讯中共享变量与Queue的优劣详解

共享变量的优劣

共享变量是线程间直接共享内存数据的方式,适用于简单场景,但需注意线程安全问题。优点是访问速度快,无需额外数据结构;缺点是需手动处理锁机制,容易引发竞态条件或死锁。

经典例子:多线程计数器

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:,可能出现以下情况:

  1. 线程A读取counter=100
  2. 线程B同时读取counter=100
  3. 两者各自完成+1操作后均写入101
    导致实际只完成1次有效递增

性能与正确性权衡

优势

  • 保证结果绝对正确
  • 代码简单直观

注意事项

  • 锁的粒度过大会降低并发性能
  • 避免嵌套锁以防死锁
  • 考虑更高效同步原语(如RLockSemaphore
    若不使用锁,计数器结果会因竞态条件而小于预期。共享变量在此场景需显式同步,增加复杂度。

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的方式,因为它是线程安全的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值