生成器线程安全吗?

我有一个多线程程序,在其中创建生成器函数,然后将其传递给新线程。 我希望它本质上是共享的/全局的,以便每个线程都可以从生成器获取下一个值。

使用这样的生成器是否安全,还是会遇到从多个线程访问共享生成器的问题/情况?

如果不是,是否有更好的方法来解决该问题? 我需要可以遍历列表并为任何线程调用它生成下一个值的东西。

4个解决方案
52 votes

它不是线程安全的; 同时调用可能交错,并与局部变量混淆。

常用的方法是使用主从模式(在PC中现在称为“农民工模式”)。 创建第三个线程来生成数据,并在主服务器和从服务器之间添加一个队列,从服务器将从队列中读取数据,而主服务器将向该队列中写入数据。 标准队列模块提供必要的线程安全性,并安排阻塞主服务器,直到从服务器准备读取更多数据为止。

Martin v. Löwis answered 2020-08-09T21:59:53Z
45 votes

编辑以在下面添加基准。

您可以用锁包装发生器。 例如,

import threading
class LockedIterator(object):
    def __init__(self, it):
        self.lock = threading.Lock()
        self.it = it.__iter__()

    def __iter__(self): return self

    def next(self):
        self.lock.acquire()
        try:
            return self.it.next()
        finally:
            self.lock.release()

gen = [x*2 for x in [1,2,3,4]]
g2 = LockedIterator(gen)
print list(g2)

锁定需要50毫秒,而我的队列需要350毫秒。 当您确实有队列时,队列很有用; 例如,如果您有传入的HTTP请求,并且希望将它们排队以供工作线程处理。 (这在Python迭代器模型中不适合-迭代器用完所有项就完成了。)如果确实有一个迭代器,则LockedIterator是使线程安全的一种更快,更简单的方法。

from datetime import datetime
import threading
num_worker_threads = 4

class LockedIterator(object):
    def __init__(self, it):
        self.lock = threading.Lock()
        self.it = it.__iter__()

    def __iter__(self): return self

    def next(self):
        self.lock.acquire()
        try:
            return self.it.next()
        finally:
            self.lock.release()

def test_locked(it):
    it = LockedIterator(it)
    def worker():
        try:
            for i in it:
                pass
        except Exception, e:
            print e
            raise

    threads = []
    for i in range(num_worker_threads):
        t = threading.Thread(target=worker)
        threads.append(t)
        t.start()

    for t in threads:
        t.join()

def test_queue(it):
    from Queue import Queue
    def worker():
        try:
            while True:
                item = q.get()
                q.task_done()
        except Exception, e:
            print e
            raise

    q = Queue()
    for i in range(num_worker_threads):
         t = threading.Thread(target=worker)
         t.setDaemon(True)
         t.start()

    t1 = datetime.now()

    for item in it:
        q.put(item)

    q.join()

start_time = datetime.now()
it = [x*2 for x in range(1,10000)]

test_locked(it)
#test_queue(it)
end_time = datetime.now()
took = end_time-start_time
print "took %.01f" % ((took.seconds + took.microseconds/1000000.0)*1000)
Glenn Maynard answered 2020-08-09T22:00:22Z
6 votes

不,它们不是线程安全的。 您可以在以下位置找到有关生成器和多线程的有趣信息:

[HTTP://呜呜呜.dab EA在.com/generators/generators.PDF]

Mikhail Churbanov answered 2020-08-09T22:00:47Z
-11 votes

这取决于您所使用的python实现。 在CPython中,GIL使对python对象的所有操作都成为线程安全的,因为在任何给定时间只能有一个线程在执行代码。

[HTTP://恩.Wikipedia.org/wiki/global_interpreter_lock]

Algorias answered 2020-08-09T22:01:11Z
translate from https://stackoverflow.com:/questions/1131430/are-generators-threadsafe