【问题标题】:should I worry about concurrent access to a dict in a multithreaded python script?我应该担心在多线程 python 脚本中对 dict 的并发访问吗?
【发布时间】:2013-11-13 19:25:54
【问题描述】:

我想通过启动多个独立的异步操作线程来加速脚本的执行,否则这些线程会一个接一个地启动。

我使用了the example from concurrent.future docs 并将其改编为我的代码:

import concurrent.futures

def myfunc(elem):
    elem['ascii'] = ord(elem['name'])

mylist = [
    {'name': 'a'},
    {'name': 'b'},
    {'name': 'c'},
    {'name': 'd'},
    {'name': 'e'}
    ]

with concurrent.futures.ThreadPoolExecutor(max_workers=3) as executor:
    future_to_url = {executor.submit(myfunc, elem): elem for elem in mylist}
    for future in concurrent.futures.as_completed(future_to_url):
        try:
            future.result()
        except Exception as exc:
            print('error: '.format(exc))

print mylist

代码按预期工作,但我是否应该担心对mylist 的并发访问,或者是否正确锁定并以串行方式访问(或任何正确的方式以使数据保持一致)?

在实际程序中,字典会大得多,我想启动约 500 名工人。

【问题讨论】:

  • 您的示例中的多个线程不使用列表和字典:列表仅由主线程访问,每个线程都使用自己的字典。
  • 是的,每个线程都将使用列表mylist 中的自己的字典。我担心的是,当修改字典时,列表也会间接更新,可能同时由多个线程(除非它被实现为某种指向字典的指针列表,在这种情况下它不会改变)
  • 为什么改变一个恰好是列表中的项目的对象会以任何方式影响列表?例如,您可以使用元组(不可变对象)而不是列表,它不会破坏您的示例
  • @J.F. Sebastian:我不知道,这就是我问这个问题的原因。我可以想象一个实现,其中列表及其内容是内存中的一大块(而不是指针列表(在 C 语义中)),并且其中一个组件的任何更改都会更改整个列表,这可能会在并发中产生问题使用权。很高兴知道 Python 在内部和外部一样出色 :)
  • 此行为不依赖于实现。一切都是 Python 中的对象(无论是 CPython、Pypy、IronPython、Jython),因此需要“引用”语义。有些容器可以将它们的项目作为普通 C 类型存储在“内存中的一大块”中——bytearrayarray.arraynumpy 数组并在访问时创建相应的 Python 对象。它仍然不会改变语义,因为相应的 Python 对象是不可变的(int、float、bytes)。

标签: python multithreading dictionary concurrency


【解决方案1】:

我想通过启动多个线程来加快脚本的执行速度

由于challenges posed by CPython's implementation,如果您对性能感兴趣,您可能应该使用ProcessPoolExecutor。请注意,这将需要在工作人员如何与应共享的数据结构进行通信和/或交互方面更加复杂的设计。

现在,关于你的问题:

我是否应该担心对 mylist 的并发访问,或者是否正确锁定并以串行方式访问(或任何正确的方式以使数据保持一致)

list 将在 多线程 环境中正常运行,但如果您有任何需要原子性的语义分层,您将需要自己的锁定。例如,假设您的设计要求/期望list 应始终具有七个元素,并且一些工作人员将执行pop() 后跟append()。您将需要自己的锁来保护工作人员免受它们之间的并发访问。

【讨论】:

  • 我当前的(串行)实现不是最优的,因为每次调用该函数都意味着网络超时。启动 n 个线程可以加快 n 次执行(线程将“一起等待”超时)。也就是说,收益不会来自脚本本身,而是来自并行化网络超时。至于你的第二点:列表长度不会改变,唯一的修改是在一些字典键上(非常符合示例的精神)
  • 可以将线程用于 IO 密集型任务,甚至用于 CPU 密集型任务(如果 GIL 是由 numpy、regex、lxml 等 C 扩展发布的)
  • @J.F.Sebastian,我认为多核目标的多线程 I/O 绑定工作仍然很慢,但新版本可能已经解决了这个问题。 See also.
猜你喜欢
  • 2019-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-07-16
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多