16

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

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

4

2 回答 2

16

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

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

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

另请注意,文档没有提到itertools用 C 实现的。我们知道(作为实现细节)CPythonitertools实际上是用 C 编写的,但是在其他实现中,它们可以很高兴地被实现为生成器,您可以回到问题你引用了

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

于 2011-08-16T19:02:39.277 回答
1

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

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);and 之间lz->long_cnt = stepped_up;(或在 this 内部PyNumber_Add())没有可以切换线程的地方。这是一个如此冷酷的“慢模式”。

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

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

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

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

于 2020-01-13T16:42:00.030 回答