最近在做爬虫使用requests, 爬虫常常在运行一段时间后假死. 而且使用原生协程编写感觉语法也很不方便. 于是翻阅了一些相关文章API之类的. 最后做了如下处理来处理.
大致测试代码如下
import gevent
import time
import random
def setter(i):
sl = random.randint(1, 10)
gevent.sleep(sl)
print("setter%d sleep %d s end time %f"%(i, sl, time.time()))
return "setter%d"%i
def waiter(i):
print("print in waiter %d time %f"%(i, time.time()))
# print(a.get())
return "waiter"
x = []
t = []
# timeout = gevent.Timeout(3, False)
# timeout.start()
# try:
with gevent.Timeout(3, False) as timeout:
for i in range(10):
t.append(gevent.spawn(setter, i))
# x = gevent.joinall(t, timeout=2, raise_error=False)
x = gevent.joinall(t)
# except:
# pass
t2 = time.time()
print("end t1 %f"%time.time())
for g in t:
print("return %s, %f"%(g.get(), time.time()))
print('t2=%f'%t2)
代码中有3种超时设置
第一种:
timeout = gevent.Timeout(2, False)
timeout.start()
这种设置方式, 设为True时超时会直接跑出异常,
设为False时会正常退出管理, 但是其他依然会按顺序执行.
---->关键是. 不执行完所有协程(即使是超时的) 是不会到后面去的. 就是说一个协程假死程序就死了.
---->第一种和第二种情况. x都没有值, x=[], 只有第三种情况可以获取到值. 但是如果不打印t的值的话. 超时后python直接退出了. 所以也有可能是因为后续获取t值实际上干扰了资源分配逻辑. 此处暂不考虑这种情况. 尽量使用由gevent管理的协程.
第二种:
with gevent.Timeout(3, True) as timeout: for i in range(10): t.append(gevent.spawn(setter, i)) # x = gevent.joinall(t, timeout=2, raise_error=False) x = gevent.joinall(t)因为我们用with包裹这段代码, 所以Timeout(2, True) 的true是超时虽然会"打印出错误日志", 但不会真正抛出异常,
但是但是但是(说3遍) 并不会终止超时协程的运行. 底下的日志是这样的
setter7 sleep 1 s end time 1515146368.047282
setter2 sleep 2 s end time 1515146369.045293
TypeError: exceptions must be classes, or instances, not bool
setter5 sleep 7 s end time 1515146374.045283
setter6 sleep 8 s end time 1515146375.046286
setter8 sleep 8 s end time 1515146375.046286
setter3 sleep 9 s end time 1515146376.045284
setter1 sleep 9 s end time 1515146376.045284
setter9 sleep 10 s end time 1515146377.045292
setter4 sleep 10 s end time 1515146377.045292
setter0 sleep 10 s end time 1515146377.045292
end t1 1515146377.045292
return setter0, 1515146377.045292
return setter1, 1515146377.045292
return setter2, 1515146377.045292
return setter3, 1515146377.045292
return setter4, 1515146377.045292
return setter5, 1515146377.045292
return setter6, 1515146377.045292
return setter7, 1515146377.045292
return setter8, 1515146377.045292
return setter9, 1515146377.045292
t2=1515146377.045292
可以看到超时时抛出错误日志. 但是仍然等待所有协程执行完后才退出with. 依然不实用.
当把True改为False后代码现在是这样
with gevent.Timeout(3, False) as timeout: for i in range(10): t.append(gevent.spawn(setter, i)) # x = gevent.joinall(t, timeout=2, raise_error=False) x = gevent.joinall(t)打印日志是这样:
setter3 sleep 1 s end time 1515146481.878118
setter8 sleep 3 s end time 1515146483.877226
end t1 1515146483.877226
setter9 sleep 4 s end time 1515146484.878255
setter5 sleep 5 s end time 1515146485.877226
setter7 sleep 5 s end time 1515146485.877226
setter1 sleep 6 s end time 1515146486.877241
setter4 sleep 7 s end time 1515146487.877250
setter0 sleep 7 s end time 1515146487.877250
return setter0, 1515146487.877250
return setter1, 1515146487.877250
setter2 sleep 10 s end time 1515146490.877226
setter6 sleep 10 s end time 1515146490.877226
return setter2, 1515146490.877226
return setter3, 1515146490.877226
return setter4, 1515146490.877226
return setter5, 1515146490.877226
return setter6, 1515146490.877226
return setter7, 1515146490.877226
return setter8, 1515146490.877226
return setter9, 1515146490.877226
t2=1515146483.877226
注意看 end t1 1515146483.877226 这一行的位置. 说明程序退出了with代码块. 但是在执行 for g in t: 之前依然可能被阻塞
在看
return setter0, 1515146487.877250
return setter1, 1515146487.877250
setter2 sleep 10 s end time 1515146490.877226
setter6 sleep 10 s end time 1515146490.877226
return setter2, 1515146490.877226
return setter3, 1515146490.877226
这一段日志. 说明底下 for g in t: 实际上也是有可能抢占到时间片而不是纯粹等待t执行完成. 但是此处gevent不会为for g in t: 优先分配时间片 而是可能阻塞在sleep上.
这里的逻辑感觉很奇怪. 暂不深究 可能以后会研究一下这一段的源码.
最后一种 存在2种情况
x = [] t = [] try: for i in range(10): t.append(gevent.spawn(setter, i)) x = gevent.joinall(t, timeout=3, raise_error=False) except: pass注意这里的x和t.
当打印t时, 反应和第2种情况没什么区别. 都是 运行满3秒进入for g in t: 阶段. 然后互相抢占时间片进行打印.
但打印x时 :for g in x: 终于正确的打印了未超时的协程给出的返回值
setter5 sleep 1 s end time 1515147234.150954
setter6 sleep 1 s end time 1515147234.150954
setter9 sleep 1 s end time 1515147234.150954
setter2 sleep 1 s end time 1515147234.150954
end t1 1515147236.151955
return setter5, 1515147236.151955
return setter6, 1515147236.151955
return setter9, 1515147236.151955
return setter2, 1515147236.151955
t2=1515147236.151955
所有超时的协程都被丢弃. 但是从t的情况分析 其实还是默默的后台运行. 只是python已经不再管超时协程的死活.
gevent似乎并没有提供强制中断超时协程的办法..
SO, 感觉目前的方式有可能造成假死的协程越来越多, 内存占用越来越多.最后...
也有可能是不使用超时协程的资源. 即可正确关闭. 这就要阅读源码后才能确定了.