【问题标题】:Python concurrency with concurrent.futures.ThreadPoolExecutorPython 并发与 concurrent.futures.ThreadPoolExecutor
【发布时间】:2022-11-02 02:20:18
【问题描述】:

考虑以下 sn-p:

import concurrent.futures
import time
from random import random

class Test(object):
    def __init__(self):
        self.my_set = set()

    def worker(self, name):
        temp_set = set()

        temp_set.add(name)
        temp_set.add(name*10)
        time.sleep(random() * 5)
        temp_set.add(name*10 + 1)

        self.my_set = self.my_set.union(temp_set) # question 1
        return name

    def start(self):
        result = []
        names = [1,2,3,4,5,6,7]
        with concurrent.futures.ThreadPoolExecutor(max_workers=len(names)) as executor:
            futures = [executor.submit(self.worker, x) for x in names]
            for future in concurrent.futures.as_completed(futures):
                result.append(future.result()) # question 2
  1. self.my_set 是否有可能通过标记为“问题 1”的行损坏?我相信union 是原子的,但分配不会有问题吗?

  2. 标有“问题 2”的那行有问题吗?我相信列表append 是原子的,所以也许这没问题。

    我已经阅读了这些文档:

    https://docs.python.org/3/library/stdtypes.html#set https://web.archive.org/web/20201101025814id_/http://effbot.org/zone/thread-synchronization.htm Is Python variable assignment atomic? https://docs.python.org/3/glossary.html#term-global-interpreter-lock

    并执行了这个问题中提供的 sn-p 代码,但我找不到在这种情况下并发应该如何工作的明确答案。

【问题讨论】:

  • 问题 1 不是线程安全的,赋值和联合是 2 个不同的对象,它们之间可能发生线程切换。

标签: python multithreading concurrency thread-safety


【解决方案1】:

关于问题1:想想这里发生了什么:

self.my_set = self.my_set.union(temp_set)

有至少三个不同步骤的序列

  1. worker 调用获取self.my_set 的副本(对Set 对象的引用)
  2. union 函数构造一个新集合。
  3. worker 分配self.my_set 来引用新构造的集合。

    那么如果两个或多个工人同时尝试做同样的事情会发生什么呢? (注意:不是保证以这种方式发生,但它可以以这种方式发生。)

    1. 他们每个人都可以获取对原始my_set 的引用。
    2. 他们每个人都可以计算一个新集合,该集合仅由 my_set 的原始成员加上自己的贡献组成。
    3. 他们每个人都可以将其新集合分配给my_set 变量。

      问题出在第三步。如果它以这种方式发生,那么这些新集合中的每一个都只会包含创建它的一个工人的贡献。不会有一个集合包含所有工人的新贡献。当一切都结束时,my_set 只会指在这些新集合中——最后一个执行分配的线程将“获胜”——而其他新集合都将被丢弃。

      防止这种情况的一种方法是使用互斥来阻止其他线程尝试计算它们的新集合并同时更新共享变量:

      class Test(object):
          def __init__(self):
              self.my_set = set()
              self.my_set_mutex = threading.Lock()
      
          def worker(self, name):
              ...
              with self.my_set_mutex
                  self.my_set = self.my_set.union(temp_set)
              return name
      

      关于问题2:附加到列表是否是“原子的”并不重要。 result 变量是 start 方法的本地变量。在您显示的代码中,result 所引用的列表对于创建它的线程之外的任何其他线程都无法访问。除非您与其他线程共享列表,否则线程之间不会有任何干扰。

【讨论】:

    猜你喜欢
    • 2015-08-03
    • 1970-01-01
    • 2018-08-25
    • 2014-02-04
    • 2017-12-12
    • 2014-10-17
    • 1970-01-01
    • 2019-07-13
    • 1970-01-01
    相关资源
    最近更新 更多