小能豆

列表是线程安全的吗?

javascript

通常建议使用具有多个线程的队列,而不是列表和.pop()。这是因为列表不是线程安全的,还是出于其他原因?


阅读 54

收藏
2024-07-30

共1个答案

小能豆

与列表相比,通常建议使用具有多个线程的队列,因为队列提供了一种线程安全的方法来处理线程间通信和数据交换。以下是在多线程环境中首选队列的主要原因:

线程安全

  • 队列queue.QueuePython 中的类专门设计为线程安全的。它在内部处理所有必要的锁定机制,确保多个线程可以安全地添加和删除项目,而不会导致数据损坏或竞争条件。
  • 列表:Python 列表不是线程安全的。如果多个线程尝试同时修改列表(例如,使用.append().pop()等),则可能导致竞争条件和不可预测的行为,因为列表没有内置锁定机制。

使用方便

  • 队列:该类提供了诸如、、和等queue.Queue便捷方法,可简化生产者-消费者模式的实现。这些方法处理必要的同步,使代码更易于编写和维护。put()``get()``task_done()``join()
  • 列表:如果使用列表,则必须手动使用threading.Lock或其他同步原语实现锁定以确保线程安全。这增加了复杂性并增加了出错的可能性。

阻塞和同步

  • 队列:该类queue.Queue支持阻塞操作。例如,get()可以阻塞直到有项目可用,也put()可以阻塞直到有空间。这对于实现生产者和消费者可以以不同速率工作的生产者-消费者模式非常有用。
  • 列表:使用列表实现阻塞行为需要额外的努力和同步原语,例如threading.Condition,来管理阻塞和通知逻辑。

例子

下面通过一个例子来说明在多线程环境中使用队列和列表的区别:

使用queue.Queue(推荐)

import threading
import queue
import time

# Create a queue
q = queue.Queue()

# Producer function
def producer():
    for i in range(10):
        item = f"item {i}"
        q.put(item)
        print(f"Produced {item}")
        time.sleep(1)

# Consumer function
def consumer():
    while True:
        item = q.get()
        if item is None:
            break  # Sentinel value to exit
        print(f"Consumed {item}")
        q.task_done()

# Create threads
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)

# Start threads
producer_thread.start()
consumer_thread.start()

# Wait for producer to finish
producer_thread.join()

# Signal consumer to exit
q.put(None)
consumer_thread.join()

使用列表(不推荐)

import threading
import time

# Create a shared list and a lock
shared_list = []
lock = threading.Lock()

# Producer function
def producer():
    for i in range(10):
        item = f"item {i}"
        with lock:
            shared_list.append(item)
        print(f"Produced {item}")
        time.sleep(1)

# Consumer function
def consumer():
    while True:
        with lock:
            if shared_list:
                item = shared_list.pop(0)
                print(f"Consumed {item}")
            else:
                time.sleep(0.1)

# Create threads
producer_thread = threading.Thread(target=producer)
consumer_thread = threading.Thread(target=consumer)

# Start threads
producer_thread.start()
consumer_thread.start()

# Wait for producer to finish
producer_thread.join()

# Join the consumer thread (not easy to terminate cleanly)
consumer_thread.join()

在基于列表的示例中,由于必须手动管理锁定,因此会产生额外的复杂性。这很容易出错,而且更难维护。另一方面,基于队列的示例利用了 的内置线程安全性和便捷方法queue.Queue,从而产生了更干净、更可靠的代码。

2024-07-30