【问题标题】:multithreading: Why aren't generators thread-safe? What happens when it is shared among threads?多线程:为什么生成器不是线程安全的?当它在线程之间共享时会发生什么?
【发布时间】:2013-12-01 08:21:55
【问题描述】:

我正在阅读this question,它询问生成器是否是线程安全的,一个答案说:

它不是线程安全的;同时通话可能交错,并与 局部变量。

Another answer 表明您可以使用锁来确保一次只有一个线程使用生成器。

我是多线程新手。谁能设计一个例子来说明当你使用没有锁的生成器时究竟会发生什么?

例如,如果我这样做似乎没有任何问题:

import threading

def generator():
    for i in data:
        yield i

class CountThread(threading.Thread):
    def __init__(self, name):
        threading.Thread.__init__(self)
        self.name = name

    def run(self):
        for i in gen():
            print '{0} {1}'.format(self.name, i)

data = [i for i in xrange(100)]
gen = generator()
a = CountThread('a')
b = CountThread('b')
a.start()
b.start()

【问题讨论】:

  • 很难展示由于线程而无法工作的示例,因为您永远不知道线程将获得多少时间或它们将按什么顺序运行。这可能是运气不好,什么都没有坏事发生了。
  • 说真的,不要一遍又一遍地问同一个问题。 (stackoverflow.com/questions/20042534/…) 如果您有什么不明白的地方,请过来把这个对话转移到Python Chat Room
  • @InbarRose 他们是同一个问题吗?我在问失败时会发生什么。
  • @InbarRose:这个问题很好,和他在你的链接问题中问的不同。

标签: python multithreading generator


【解决方案1】:

运行这个例子。

您会看到这 10 000 个数字将在线程间“共享”。您不会在两个线程中看到 10 000 个数字。

实际上很可能一个线程会看到所有的数字。

import threading

class CountThread(threading.Thread):
  def __init__(self, gen):
      threading.Thread.__init__(self)
      self.gen = gen
      self.numbers_seen = 0

  def run(self):
      for i in self.gen:
          self.numbers_seen += 1


def generator(data):
    for _ in data:
        yield data

gen = generator(xrange(10000))

a = CountThread(gen)
b = CountThread(gen)

a.start()
b.start()

a.join()
b.join()

print "Numbers seen in a", a.numbers_seen
print "Numbers seen in b", b.numbers_seen

实际上,如果Python在执行过程中发生切换线程(只要使用大于10000的值,例如10000000),就会出现异常:

Exception in thread Thread-2:
Traceback (most recent call last):
  File "/usr/local/Cellar/python/2.7.5/Frameworks/Python.framework/Versions/2.7/lib/python2.7/threading.py", line 808, in __bootstrap_inner
    self.run()
  File "test.py", line 10, in run
    for i in self.gen:
ValueError: generator already executing

【讨论】:

  • 有趣的是,如果你使用内置的 iter() 函数而不是你的 generator() 函数——就像在 gen = iter(xrange(100000000)) 中一样——它似乎总是毫无例外地让人想通.我并不是说你错了——也许从iter() 返回的对象是线程安全的,尽管文档中没有提到这一点。
  • iter() 不是生成器,它是一个完全自定义的迭代器,可以维护自己的状态(与生成器相比,生成器必须更通用地执行此操作)
  • @Nick Bastin: A generator is just a function which returns an iterator,所以由于iter()内置函数returns an iterator object,根据定义它一个生成器。
  • @martineau:不是真的 - 它可能看起来像鸭子(发电机)和嘎嘎像鸭子(发电机),但内部实现完全不同,因此没有与完全用 python 创建的生成器(iter() 完全用 C 实现)。 python 生成器(使用yield 创建的)继承了CPython 实现固有的线程安全问题,但由于iter() 完全在C 中实现,它可以避免调用back时必须是线程安全的问题> 进入你的 python 代码以获得下一个值。
  • @NickBastin:是的,我知道这是因为iter() 是用C 实现的。正如我最初评论的那样,我只是觉得它很有趣。但是现在我想知道是否有某种方法可以继承或以其他方式利用它的线程安全性来制作自己的使用yield 创建的纯 python 生成器也是这样......
猜你喜欢
  • 2018-12-11
  • 2017-02-14
  • 1970-01-01
  • 2013-10-13
  • 2013-06-09
  • 2016-05-20
  • 2019-02-11
  • 1970-01-01
  • 2018-10-25
相关资源
最近更新 更多