Python 发电机是否安全?
我有一个多线程程序,我在其中创建一个生成器函数,然后将其传递给新线程。我希望它在本质上是共享/全局的,这样每个线程都可以从生成器中获得下一个值 使用这样的生成器是否安全,或者从多个线程访问共享生成器时是否会遇到问题/情况Python 发电机是否安全?,python,multithreading,thread-safety,generator,Python,Multithreading,Thread Safety,Generator,我有一个多线程程序,我在其中创建一个生成器函数,然后将其传递给新线程。我希望它在本质上是共享/全局的,这样每个线程都可以从生成器中获得下一个值 使用这样的生成器是否安全,或者从多个线程访问共享生成器时是否会遇到问题/情况 如果没有,有没有更好的方法解决这个问题?我需要一个能够在列表中循环并为线程调用的任何线程生成下一个值的东西。它不是线程安全的;同时调用可能会交错,并弄乱局部变量 常用的方法是使用主从模式(现在在PC中称为农民工模式)。创建生成数据的第三个线程,并在主线程和从线程之间添加一个队列
如果没有,有没有更好的方法解决这个问题?我需要一个能够在列表中循环并为线程调用的任何线程生成下一个值的东西。它不是线程安全的;同时调用可能会交错,并弄乱局部变量
常用的方法是使用主从模式(现在在PC中称为农民工模式)。创建生成数据的第三个线程,并在主线程和从线程之间添加一个队列,从线程从队列中读取数据,主线程向队列中写入数据。标准队列模块提供必要的线程安全,并安排阻止主队列,直到从队列准备好读取更多数据。否,它们不是线程安全的。您可以在以下网站中找到有关生成器和多线程的有趣信息:
这取决于您使用的python实现。在CPython中,GIL使python对象上的所有操作都是线程安全的,因为在任何给定时间只有一个线程可以执行代码
编辑以在下面添加基准 你可以用锁把发电机包起来。比如说,
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迭代器模型——一旦迭代器的项用完,它就完成了。)如果确实有迭代器,那么LockEditor是一种更快速、更简单的方法,可以使它实现线程安全
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)
生成器对象本身与受GIL保护的任何PyObject一样是线程安全的。但是,试图从生成器中获取下一个元素的线程(在
yield
之间执行生成器代码)将获得ValueError:
ValueError: generator already executing
示例代码:
from threading import Thread
from time import sleep
def gen():
sleep(1)
yield
g = gen()
Thread(target=g.__next__).start()
Thread(target=g.__next__).start()
结果:
Exception in thread Thread-2:
Traceback (most recent call last):
File "/usr/lib/python3.8/threading.py", line 932, in _bootstrap_inner
self.run()
File "/usr/lib/python3.8/threading.py", line 870, in run
self._target(*self._args, **self._kwargs)
ValueError: generator already executing
但是,实际上这与线程无关。并且可以在单个线程内复制:
def gen():
yield next(g)
g = gen()
next(g)
由IIRC python freenode提供,这里是Python3.x的一个有效解决方案 默认情况下,生成器不是线程安全的,但下面介绍如何使它们成为线程安全的
def my_generator():
while True:
for x in range(10):
yield x
或者使用一个函数
def threadsafe_iter(iterable):
lock = threading.Lock()
iterator = iter(iterable)
while True:
with lock:
for value in iterator:
break
else:
return
yield value
n = threadsafe_iter(my_generator)
next(n)
next(n)
next(n)
对于Queue.Queue,绝对是+1,这是组织线程系统的好方法(在大多数情况下,对于这个任务也是如此)。“GIL使python对象上的所有操作都是线程安全的”-嗯?并非所有操作都是原子式的,这是危险的误导。吉尔只意味着Python代码不会在多线程环境中破坏Python状态:不能在字节码OP的中间更改线程。(例如,可以修改共享DATT而不损坏它)。您仍然可以在任意两个字节码OPS之间更改线程。GIL不能阻止两个线程改变共享资源。GIL只阻止线程的并行执行,你仍然需要处理并发访问和任意线程切换。效率不如使用Queue.Queue,但做得很漂亮。
def threadsafe_iter(iterable):
lock = threading.Lock()
iterator = iter(iterable)
while True:
with lock:
for value in iterator:
break
else:
return
yield value
n = threadsafe_iter(my_generator)
next(n)
next(n)
next(n)