【问题标题】:Is itertools thread-safe?itertools 是线程安全的吗?
【发布时间】:2011-10-28 08:11:23
【问题描述】:

例如,如果我使用chain 创建一个迭代器,我可以在多个线程上调用它吗?请注意,依赖 GIL 的线程安全是可以接受的,但不是可取的。

(请注意,这与this question 有点不同,this question 处理的是生成器,而不是用 C 编写的迭代器)。

【问题讨论】:

    标签: python thread-safety itertools


    【解决方案1】:

    首先,official documentation on itertools 中没有任何内容表明它们是线程安全的。因此,按照规范,Python 似乎对此没有任何保证。这在 Jython 或 PyPy 等实现中可能有所不同,但这意味着您的代码可能无法移植。

    其次,大多数itertools(除了简单的,如count)将其他迭代器作为输入。您还需要这些迭代器以线程安全的方式正确运行。

    第三,一些迭代器在被不同线程同时使用时可能没有意义。例如,izip 在多个线程中工作可能会进入竞争条件,从多个来源获取元素,尤其是由等效的 python 代码定义的(当一个线程设法从一个输入迭代器中获取值,然后从两个输入迭代器中获取第二个线程时会发生什么) ?)。

    还要注意,文档没有提到 itertools 是用 C 实现的。我们知道(作为实现细节)CPython 的 itertools 实际上是用 C 编写的,但在其他实现中,它们可以很高兴地被实现为生成器,您可以返回question you cited

    所以,不,除非您知道目标 python 平台的实现细节,否则您不能假设它们是线程安全的。

    【讨论】:

      【解决方案2】:

      当前的实现似乎是原子的(线程安全的)

      CPython-3.8,https://github.com/python/cpython/blob/v3.8.1/Modules/itertoolsmodule.c#L4129

      static PyTypeObject count_type = {
          PyVarObject_HEAD_INIT(NULL, 0)
          "itertools.count",                  /* tp_name */
          sizeof(countobject),                /* tp_basicsize */
          0,                                  /* tp_itemsize */
          /* methods */
          (destructor)count_dealloc,          /* tp_dealloc */
          0,                                  /* tp_vectorcall_offset */
          0,                                  /* tp_getattr */
          0,                                  /* tp_setattr */
          0,                                  /* tp_as_async */
          (reprfunc)count_repr,               /* tp_repr */
          0,                                  /* tp_as_number */
          0,                                  /* tp_as_sequence */
          0,                                  /* tp_as_mapping */
          0,                                  /* tp_hash */
          0,                                  /* tp_call */
          0,                                  /* tp_str */
          PyObject_GenericGetAttr,            /* tp_getattro */
          0,                                  /* tp_setattro */
          0,                                  /* tp_as_buffer */
          Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC |
              Py_TPFLAGS_BASETYPE,            /* tp_flags */
          itertools_count__doc__,             /* tp_doc */
          (traverseproc)count_traverse,       /* tp_traverse */
          0,                                  /* tp_clear */
          0,                                  /* tp_richcompare */
          0,                                  /* tp_weaklistoffset */
          PyObject_SelfIter,                  /* tp_iter */
          (iternextfunc)count_next,           /* tp_iternext */
          count_methods,                      /* tp_methods */
          0,                                  /* tp_members */
          0,                                  /* tp_getset */
          0,                                  /* tp_base */
          0,                                  /* tp_dict */
          0,                                  /* tp_descr_get */
          0,                                  /* tp_descr_set */
          0,                                  /* tp_dictoffset */
          0,                                  /* tp_init */
          0,                                  /* tp_alloc */
          itertools_count,                    /* tp_new */
          PyObject_GC_Del,                    /* tp_free */
      };
      
      // ... ... ...
      
      static PyObject *
      count_nextlong(countobject *lz)
      {
          PyObject *long_cnt;
          PyObject *stepped_up;
      
          long_cnt = lz->long_cnt;
          if (long_cnt == NULL) {
              /* Switch to slow_mode */
              long_cnt = PyLong_FromSsize_t(PY_SSIZE_T_MAX);
              if (long_cnt == NULL)
                  return NULL;
          }
          assert(lz->cnt == PY_SSIZE_T_MAX && long_cnt != NULL);
      
          stepped_up = PyNumber_Add(long_cnt, lz->long_step);
          if (stepped_up == NULL)
              return NULL;
          lz->long_cnt = stepped_up;
          return long_cnt;
      }
      
      static PyObject *
      count_next(countobject *lz)
      {
          if (lz->cnt == PY_SSIZE_T_MAX)
              return count_nextlong(lz);
          return PyLong_FromSsize_t(lz->cnt++);
      }
      

      因为在stepped_up = PyNumber_Add(long_cnt, lz->long_step);lz->long_cnt = stepped_up;(或在此PyNumber_Add() 内部)之间没有可以切换线程的地方。这是一个如此冷酷的“慢模式”。

      在“快速模式”中,PyLong_FromSsize_t(lz->cnt++) 的构造显然是原子的。

      线程安全的另一部分由 GIL 提供:

      • 当 python 字节码运行时,线程切换会在某些时候发生。在 i/o 函数中。

      • 用于消除内存重新排序副作用的内存栅栏

      【讨论】:

      • 当您说“在快速模式下......显然是原子的”时,您的意思只是因为 GIL 和没有 IO 的事实,对吗?因为在 C 级,i++ 实际上不是原子的,也不是任何函数调用。
      猜你喜欢
      • 2021-10-12
      • 2015-04-18
      • 2011-10-07
      • 2012-03-02
      • 2023-03-14
      • 2016-08-24
      • 2011-08-16
      • 2011-09-28
      • 2010-12-14
      相关资源
      最近更新 更多