一日一技:如何让 itertools.tee 线程安全

简介: 一日一技:如何让 itertools.tee 线程安全

摄影:产品经理一起吃:kingname & 产品经理

在上一篇文章中,我们说到了,itertools.tee不是线程安全的,并给出了一个例子,如下图所示:

在两个线程里面同时运行分裂出来的生成器对象,就会导致报错。

现在,你想看看itertools.tee的源代码,但是你会发现,在 PyCharm 里面,它的源代码如下图所示:

这是因为,在 CPython 中,itertools.tee底层是通过 C 语言实现的,所以你不能在 PyCharm 中看到它的源代码。但是你可以通过阅读 Python 的源代码中的 Modules/itertoolsmodule.c 文件[1],找到它的实现算法。

导致问题的核心部分在如下图所示的两段代码中:

大家看不懂也没有关系,根据我上一篇文章中使用 Python 实现的简化版本就足够帮助理解了。

我们使用简化版本来解释其中线程不安全的地方:

def generator():
    for i in range(3):
        yieldf'我是你第{i}个爷爷'
def split(g):
    value_list_1 = []
    value_list_2 = []
    def wrap(queue):
        whileTrue:
            ifnot queue:
                try:
                    value = next(g)
                except StopIteration:
                    return
                value_list_1.append(value)
                value_list_2.append(value)
            yield queue.pop(0)
    g_1 = wrap(value_list_1)
    g_2 = wrap(value_list_2)
    return g_1, g_2
g = generator()
g_1, g_2 = split(g)
for value in g_1:
    print(value)
for value in g_2:
    print(value)

当两个线程同时运行到if not queue时,发现当前各自的队列都是空的,于是进入value = next(g)获取下一个值。其中,线程 A 先进入那么几毫秒。然后线程 B 进入value = next(g)。但由于此时线程 A 中的next(g)正在运行,尚未结束,线程 B 又跑来运行,于是就导致了报错的发生。Python 中,生成器不是线程安全的。

那么如何让itertools.tee分裂出来的多个生成器可以在多线程中运行呢?其关键因素就是让value = next(g)这一行一次只能让一个线程运行。所以我们可以通过加锁来实现。

import itertools
from threading import Lock
class KingnameTee:
    def __init__(self, tee_obj, lock):
        self.tee_obj = tee_obj
        self.lock = lock
    def __iter__(self):
        return self
    def __next__(self):
        with self.lock:
            return next(self.tee_obj)
    def __copy__(self):
        return KingnameTee(self.tee_obj.__copy__(), self.lock)
def safe_tee(iterable, n=2):
    """tuple of n independent thread-safe iterators"""
    lock = Lock()
    return tuple(KingnameTee(tee_obj, lock) for tee_obj in itertools.tee(iterable, n))

我们来看看运行效果:

多线程完美运行。

目录
相关文章
|
4月前
|
Python
python sort和sorted的区别
在Python中,sort()和sorted()都是用于排序的函数,但它们之间存在一些关键的区别,这些区别主要体现在它们的应用方式、操作对象以及对原始数据的影响上。
|
4月前
|
存储 缓存 算法
Python中collections模块的deque双端队列:深入解析与应用
在Python的`collections`模块中,`deque`(双端队列)是一个线程安全、快速添加和删除元素的双端队列数据类型。它支持从队列的两端添加和弹出元素,提供了比列表更高的效率,特别是在处理大型数据集时。本文将详细解析`deque`的原理、使用方法以及它在各种场景中的应用。
|
1月前
|
测试技术 索引 Python
|
4月前
|
数据库 索引 Python
Python中collections模块的namedtuple具名元组:原理、应用与优势
在Python的`collections`模块中,`namedtuple`是一个用于创建具有名称属性的元组的工厂函数。它提供了一种更直观、更易于理解的方式来组织和访问数据。通过`namedtuple`,我们可以为元组的每个位置指定一个名字,从而使元组更加有意义和易于操作。本文将深入解析`namedtuple`的原理、使用方法以及它在实际应用中的优势。
|
安全 调度 数据安全/隐私保护
Python 列表是否线程安全?
Python中的列表不是线程安全的,在多线程环境下,对列表的操作可能会导致数据冲突或错误。但是,并非所有列表操作都是线程不安全的。如果操作是原子的,也就是说不能被线程调度机制打断,那么就没有问题。比如L.append(x)和L.pop()就是原子操作,所以是thread安全。如果操作不是原子的,或者涉及修改多个列表元素,那么就需要使用锁或者其他同步机制来保证线程安全。例如,L[i] = L[j] 和 L.append(L[- 1]) 不是原子操作,因此它们可能会导致冲突。可以使用 dis 模块来检查操作是否是原子操作。
576 0
|
Python
python深度复制字典,copy方法与deepcopy方法
python深度复制字典,copy方法与deepcopy方法
151 0
|
Python
Python中的字典与集合的底层实现原理
Python中的字典与集合的底层实现原理
118 0
golang 实现 deepcopy 的两种实现方式
golang 实现 deepcopy 的两种实现方式
576 0
|
Python
Python中itertools.combinations()的使用
来自 itertools 模块的函数 combinations(list_name, x) 将一个列表和数字 ‘x’ 作为参数,并返回一个元组列表,每个元组的长度为 ‘x’,其中包含x个元素的所有可能组合。
286 0
|
存储 Python
Python中的字典到底是有序的吗
在新的版本中,python针对key的存储已经变为有序,在遍历和打印的时候,会按照存储的顺序进行取值。之前介绍到,在字典中,key是唯一的。这里并不是说写了不唯一的key就会报错,只是会用后面的key和value去覆盖前面的key和value。
Python中的字典到底是有序的吗