写了个爬取ROE的爬虫,出现了一个问题,对迭代器对象使用threading的时候报错:generator already executing
搜了一下百度上只有两个方法,方法相同,但是代码一个有问题,现在取正确的方法记录如下:
最近写脚本时涉及到了多线程和生成器,在使用过程中遇到了数据竞争的问题,特此记录下。
最终代码如下:
import threading
class threadsafe_generator:
def __init__(self, gen):
self.gen = gen
self.lock = threading.Lock()
def __iter__(self):
return self
def __next__(self):
with self.lock:
return self.gen.__next__()
def main(code):
for i in code:
if i[1]:
x = get_roe(i[1])
print(i[0])
save(x, i[1], i[2])
else:
continue
if __name__ == '__main__':
code = threadsafe_generator(get_code()) # 生成新的迭代器
years = int(input('请输入年数:'))
roes = float(input('请输入roe:'))
tasks = []
for t in range(10):
t = threading.Thread(target=main, args=(code,))
t.start()
tasks.append(t)
for task in tasks:
task.join()
在执行脚本时提示以下错误:ValueError: generator already executing
多线程同时请求生成器数据(即同时调用next方法)就会引发该错误,所以只要在调用next方法时加个锁就可以解决。
在脚本开头处加入:
class threadsafe_iter:
"""Takes an iterator/generator and makes it thread-safe by
serializing call to the `next` method of given iterator/generator.
"""
def __init__(self, it):
self.it = it
self.lock = threading.Lock()
def __iter__(self):
return self
def __next__(self): # python3
with self.lock:
return self.it.__next__()
# def next(self): # python2
# with self.lock:
# return self.it.next()
def threadsafe_generator(f):
"""A decorator that takes a generator function and makes it thread-safe.
"""
def g(*a, **kw):
return threadsafe_iter(f(*a, **kw))
return g
说明:
由于next方法的不同,所以需要区别python环境。
threadsafe_generator为装饰器,使用时只需在生成器上加入@threadsafe_generator即可。
具体的例子可以参考:https://gist.github.com/platdrag/e755f3947552804c42633a99ffd325d4
以上为简书的内容,解决方法是创建一个类,但没写后续用法。
结合另外一个帖子的内容,得到最终正确用法
import threading
class threadsafe_generator:
"""Takes an generator and makes it thread-safe by
serializing call to the `next` method of given generator.
"""
def __init__(self, gen):
self.gen = gen
self.lock = threading.Lock()
def __iter__(self):
return self.next()
def next(self):
with self.lock:
return self.gen.next()
# 定义我们的生成器
def all_keywords():
for row in csv.reader(open('companies.csv')):
if row[0]:
yield row[0]
# 将其转换为线程安全的
keywords = threadsafe_generator(all_keywords())
# 然后在线程中就可以随意地使用keywords.next()而不必担心"generator already executing"异常了。
最后的方法就是用新类创建一个新的迭代器对象,后边这种方法的代码是错误的,第一种方法正确,但是没有用法,需要两种结合一下
参考链接:https://www.jianshu.com/p/79dcfd77e56c
参考链接:http://www.redicecn.com/html/Python/20120619/417.html