import unittest
+from threading import Thread, Barrier
from itertools import (
+ accumulate,
+ chain,
+ combinations_with_replacement,
+ cycle,
+ permutations,
tee,
+ zip_longest,
)
from test.support import threading_helper
threading_helper.requires_working_threading(module=True)
+def work_iterator(it):
+ while True:
+ try:
+ next(it)
+ except StopIteration:
+ break
+
+
+class ItertoolsThreading(unittest.TestCase):
+
+ @threading_helper.reap_threads
+ def test_cycle(self):
+ number_of_threads = 6
+ number_of_iterations = 10
+ number_of_cycles = 400
+
+ barrier = Barrier(number_of_threads)
+ def work(it):
+ barrier.wait()
+ for _ in range(number_of_cycles):
+ _ = next(it)
+
+ data = (1, 2, 3, 4)
+ for it in range(number_of_iterations):
+ cycle_iterator = cycle(data)
+ worker_threads = []
+ for ii in range(number_of_threads):
+ worker_threads.append(
+ Thread(target=work, args=[cycle_iterator]))
+
+ with threading_helper.start_threads(worker_threads):
+ pass
+
+ barrier.reset()
+
+ @threading_helper.reap_threads
+ def test_chain(self):
+ number_of_threads = 6
+ number_of_iterations = 20
+
+ barrier = Barrier(number_of_threads)
+ def work(it):
+ barrier.wait()
+ while True:
+ try:
+ next(it)
+ except StopIteration:
+ break
+
+ data = [(1, )] * 200
+ for it in range(number_of_iterations):
+ chain_iterator = chain(*data)
+ worker_threads = []
+ for ii in range(number_of_threads):
+ worker_threads.append(
+ Thread(target=work, args=[chain_iterator]))
+
+ with threading_helper.start_threads(worker_threads):
+ pass
+
+ barrier.reset()
+
+ @threading_helper.reap_threads
+ def test_combinations_with_replacement(self):
+ number_of_iterations = 6
+ for _ in range(number_of_iterations):
+ it = combinations_with_replacement(tuple(range(2)), 2)
+ threading_helper.run_concurrently(work_iterator, nthreads=6, args=[it])
+
+ @threading_helper.reap_threads
+ def test_permutations(self):
+ number_of_iterations = 6
+ for _ in range(number_of_iterations):
+ it = permutations(tuple(range(2)), 2)
+ threading_helper.run_concurrently(work_iterator, nthreads=6, args=[it])
+
+ @threading_helper.reap_threads
+ def test_accumulate(self):
+ number_of_iterations = 10
+ for _ in range(number_of_iterations):
+ it = accumulate(tuple(range(40)))
+ threading_helper.run_concurrently(work_iterator, nthreads=10, args=[it])
+
+ @threading_helper.reap_threads
+ def test_zip_longest(self):
+ number_of_iterations = 10
+ for _ in range(number_of_iterations):
+ it = zip_longest(list(range(4)), list(range(8)), fillvalue=0)
+ threading_helper.run_concurrently(work_iterator, nthreads=10, args=[it])
+
+
+
class TestTeeConcurrent(unittest.TestCase):
# itertools.tee branches share a linked list of internal data cells.
# Concurrent iteration must not corrupt that shared state or crash the
--- /dev/null
+import unittest
+from threading import Thread, Barrier
+from itertools import combinations, product
+from test.support import threading_helper
+
+
+threading_helper.requires_working_threading(module=True)
+
+def test_concurrent_iteration(iterator, number_of_threads):
+ barrier = Barrier(number_of_threads)
+ def iterator_worker(it):
+ barrier.wait()
+ while True:
+ try:
+ _ = next(it)
+ except StopIteration:
+ return
+
+ worker_threads = []
+ for ii in range(number_of_threads):
+ worker_threads.append(
+ Thread(target=iterator_worker, args=[iterator]))
+
+ with threading_helper.start_threads(worker_threads):
+ pass
+
+ barrier.reset()
+
+class ItertoolsThreading(unittest.TestCase):
+
+ @threading_helper.reap_threads
+ def test_combinations(self):
+ number_of_threads = 10
+ number_of_iterations = 24
+
+ for it in range(number_of_iterations):
+ iterator = combinations((1, 2, 3, 4, 5), 2)
+ test_concurrent_iteration(iterator, number_of_threads)
+
+ @threading_helper.reap_threads
+ def test_product(self):
+ number_of_threads = 10
+ number_of_iterations = 24
+
+ for it in range(number_of_iterations):
+ iterator = product((1, 2, 3, 4, 5), (10, 20, 30))
+ test_concurrent_iteration(iterator, number_of_threads)
+
+
+if __name__ == "__main__":
+ unittest.main()
PyObject *it;
PyObject *saved;
Py_ssize_t index;
- int firstpass;
} cycleobject;
#define cycleobject_CAST(op) ((cycleobject *)(op))
}
lz->it = it;
lz->saved = saved;
- lz->index = 0;
- lz->firstpass = 0;
+ lz->index = -1;
return (PyObject *)lz;
}
cycleobject *lz = cycleobject_CAST(op);
PyObject *item;
- if (lz->it != NULL) {
+ Py_ssize_t index = FT_ATOMIC_LOAD_SSIZE_RELAXED(lz->index);
+
+ if (index < 0) {
item = PyIter_Next(lz->it);
if (item != NULL) {
- if (lz->firstpass)
- return item;
if (PyList_Append(lz->saved, item)) {
Py_DECREF(item);
return NULL;
/* Note: StopIteration is already cleared by PyIter_Next() */
if (PyErr_Occurred())
return NULL;
+ index = 0;
+ FT_ATOMIC_STORE_SSIZE_RELAXED(lz->index, 0);
+#ifndef Py_GIL_DISABLED
Py_CLEAR(lz->it);
+#endif
}
if (PyList_GET_SIZE(lz->saved) == 0)
return NULL;
- item = PyList_GET_ITEM(lz->saved, lz->index);
- lz->index++;
- if (lz->index >= PyList_GET_SIZE(lz->saved))
- lz->index = 0;
- return Py_NewRef(item);
+ item = PyList_GetItemRef(lz->saved, index);
+ assert(item);
+ index++;
+ if (index >= PyList_GET_SIZE(lz->saved)) {
+ index = 0;
+ }
+ FT_ATOMIC_STORE_SSIZE_RELAXED(lz->index, index);
+ return item;
}
static PyType_Slot cycle_slots[] = {
return 0;
}
-static PyObject *
-chain_next(PyObject *op)
+static inline PyObject *
+chain_next_lock_held(PyObject *op)
{
chainobject *lz = chainobject_CAST(op);
PyObject *item;
return NULL;
}
+static PyObject *
+chain_next(PyObject *op)
+{
+ PyObject *result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = chain_next_lock_held(op);
+ Py_END_CRITICAL_SECTION()
+ return result;
+}
+
PyDoc_STRVAR(chain_doc,
"chain(*iterables)\n\
--\n\
}
static PyObject *
-product_next(PyObject *op)
+product_next_lock_held(PyObject *op)
{
productobject *lz = productobject_CAST(op);
PyObject *pool;
return NULL;
}
+static PyObject *
+product_next(PyObject *op)
+{
+ PyObject *result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = product_next_lock_held(op);
+ Py_END_CRITICAL_SECTION()
+ return result;
+}
+
static PyMethodDef product_methods[] = {
{"__sizeof__", product_sizeof, METH_NOARGS, sizeof_doc},
{NULL, NULL} /* sentinel */
}
static PyObject *
-combinations_next(PyObject *op)
+combinations_next_lock_held(PyObject *op)
{
combinationsobject *co = combinationsobject_CAST(op);
PyObject *elem;
return NULL;
}
+static PyObject *
+combinations_next(PyObject *op)
+{
+ PyObject *result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = combinations_next_lock_held(op);
+ Py_END_CRITICAL_SECTION()
+ return result;
+}
+
static PyMethodDef combinations_methods[] = {
{"__sizeof__", combinations_sizeof, METH_NOARGS, sizeof_doc},
{NULL, NULL} /* sentinel */
}
static PyObject *
-cwr_next(PyObject *op)
+cwr_next_lock_held(PyObject *op)
{
cwrobject *co = cwrobject_CAST(op);
PyObject *elem;
return NULL;
}
+static PyObject *
+cwr_next(PyObject *op)
+{
+ PyObject *result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = cwr_next_lock_held(op);
+ Py_END_CRITICAL_SECTION()
+ return result;
+}
+
static PyMethodDef cwr_methods[] = {
{"__sizeof__", cwr_sizeof, METH_NOARGS, sizeof_doc},
{NULL, NULL} /* sentinel */
}
static PyObject *
-permutations_next(PyObject *op)
+permutations_next_lock_held(PyObject *op)
{
permutationsobject *po = permutationsobject_CAST(op);
PyObject *elem;
return NULL;
}
+static PyObject *
+permutations_next(PyObject *op)
+{
+ PyObject *result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = permutations_next_lock_held(op);
+ Py_END_CRITICAL_SECTION()
+ return result;
+}
+
static PyMethodDef permuations_methods[] = {
{"__sizeof__", permutations_sizeof, METH_NOARGS, sizeof_doc},
{NULL, NULL} /* sentinel */
}
static PyObject *
-accumulate_next(PyObject *op)
+accumulate_next_lock_held(PyObject *op)
{
accumulateobject *lz = accumulateobject_CAST(op);
PyObject *val, *newtotal;
return newtotal;
}
+static PyObject *
+accumulate_next(PyObject *op)
+{
+ PyObject *result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = accumulate_next_lock_held(op);
+ Py_END_CRITICAL_SECTION()
+ return result;
+}
+
static PyType_Slot accumulate_slots[] = {
{Py_tp_dealloc, accumulate_dealloc},
{Py_tp_getattro, PyObject_GenericGetAttr},
}
static PyObject *
-zip_longest_next(PyObject *op)
+zip_longest_next_lock_held(PyObject *op)
{
ziplongestobject *lz = ziplongestobject_CAST(op);
Py_ssize_t i;
return result;
}
+static PyObject *
+zip_longest_next(PyObject *op)
+{
+ PyObject *result;
+ Py_BEGIN_CRITICAL_SECTION(op);
+ result = zip_longest_next_lock_held(op);
+ Py_END_CRITICAL_SECTION()
+ return result;
+}
+
PyDoc_STRVAR(zip_longest_doc,
"zip_longest(*iterables, fillvalue=None)\n\
--\n\