日常前言
最近接 到一个抢票的爬虫外包,那个网站及其之捞,访问购票地址竟然还要排队,在购票高峰临时升一下服务器配置不行吗…没办法,甲方爸爸的要求还得做啊,其中一个障碍便是目标网站的后端限制了访问频次,俗话说:“上有政策,下有对策。” 立刻想到了多线程 + 多代理
的方式进行访问。
但此时问题便来了,多代理还好说,再写个爬虫爬一堆下来就好,多线程可就麻烦多了,多线程一旦发出去了,基本等同于失控的状态,你无法去结束或者是重启一个线程,最多只能是获取线程的信息,没有实际的控制权,而且Python官方也没有提供相应的结束函数。那么接下来,让我们来好好聊聊解决这个问题的思路。
单线程的结束
说实话,会百度在程序世界是一个优秀的习惯,不然怎么会有这么一张表情包呢
但是百度这一次却不尽人意,搜了很久,结果不尽人意,基本上所有的搜索结果都告诉我只有结束单个线程的方法,我也试过循环使用百度的结束函数,但最终都只能是结束的当前的这一个线程,无法达到目标。
贴一段搜到的单线程结束代码示例
def _async_raise(tid, exctype): tid = ctypes.c_long(tid) if not inspect.isclass(exctype): exctype = type(exctype) res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(exctype)) if res == 0: raise ValueError("invalid thread id") elif res != 1: ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, None) raise SystemError("PyThreadState_SetAsyncExc failed") def stop_thread(thread): _async_raise(thread.ident, SystemExit)
那怎么结束多个线程呢?
既然度娘也搜不到,那就自己探索,打开python threading模块的官方文档,其中一个daemon
属性进入了视野,单词翻译过来便是守护进程,相信大家应该或多或少的听到过,以下是官方的释义,大概意思就是只要在启动线程之前设置了这个属性为True,当父进程结束时,所有的子进程跟着全部结束,这样就好办了,接下来看看代码部分。
daemon
A boolean value indicating whether this thread is a daemon thread (True) or not (False). This must be set before start() is called, otherwise RuntimeError is raised. Its initial value is inherited from the creating thread; the main thread is not a daemon thread and therefore all threads created in the main thread default to daemon = False.
完整代码
import threading,time,random class Messy: def __init__(self): self.__messy = 0 def m(self,i): # 随机时间进行打印 time.sleep(random.random()*2) print(i) if i == 1: self.__messy = 1 def main(self): Threads = [] # 将会启动10个线程,线程id为 1 时全部线程终止! for i in range(10): t = threading.Thread(target=self.m,args=(i,)) t.daemon = 1 Threads.append(t) # 启动所有线程 for i in Threads: i.start() # 当标志位【 messy 】时所有多线程结束 while 1: if self.__messy: break print('线程已退出!') Messy().main() # 继续执行后续程序 for i in range(5): print('yeah!')
此时,main
这个函数对于多线程来讲,便是父进程,也就是守护进程。预计会进行10次循环的数字打印,但是当self.__messy
这个标志位为真时,所有的剩余子线程将不会再执行,直接结束进行后续的操作
e.g:如下图便只打印了四次
最后
目前来讲,用设置主线程退出的方法是可以完成现在这个抢票的目标。
但是后来发现其实这么做也会带来很多坏处,直接杀掉所有子线程对系统来说是一个很粗鲁的行为,如果涉及到的操作包括了文件数据、数据库数据
的改动的话,内存无法被合理释放(之前就遇到过CPU莫名占用满),极有可能造成数据丢失甚至系统中断
。
我这里只是一个抢票的小程序,子线程只用到了POST,网络请求中断带来的影响还是相对来讲比较小的,所以大家酌情使用本篇所介绍的方法。
本文作者: Messy
原文链接:https://www.messys.top/detail/78