itertools是否线程安全?

16
例如,如果我使用chain创建一个迭代器,我能在多个线程上调用它吗?请注意,依赖于GIL的线程安全是可以接受的,但不是首选方法。
(请注意,这与此问题有所不同,该问题涉及由C编写的生成器而不是迭代器)。
2个回答

16
首先,官方文档中没有说明itertools是线程安全的。因此,按照规范Python并不保证其线程安全性。虽然Jython或PyPy等实现可能存在差异,但这意味着您的代码可能不可移植。
其次,大多数itertools(除了像count这样的简单工具)需要其他迭代器作为输入。您需要确保这些迭代器也能够以线程安全的方式正确运行。
第三,当不同线程同时使用某些迭代器时,可能并不合理。例如,izip在多个线程中的使用可能会出现竞争条件,从多个源中获取元素,特别是当它由等效Python代码定义时(当一个线程只从一个输入迭代器中获取值,然后第二个线程从其中两个获取值时会发生什么?)。
此外,请注意,文档未提到itertools是用C实现的。我们知道(作为实现细节),CPython的itertools实际上是用C编写的,但在其他实现中,它们可以愉快地被实现为生成器,并且您可以回到您引用的问题
因此,除非您了解目标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函数。

  • 内存栅用于消除内存重排序副作用。


1
当你说“以快速模式运行...显然是原子性的”时,你的意思是仅仅因为GIL和没有IO吗?因为在C级别中,i++实际上并不是原子操作,也不是任何函数调用。 - Guy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接