背景:运行一个爬虫,开了10个线程,每个线程先去爬取指定数量的代理作为自己的代理池,然后开始工作。
问题:下面是爬虫日志的两行,可以看到在第一行任务处等待了45秒,而这里不过是输出一条信息,十分不理解为什么等了这么长时间?日志中像这样动辄十几秒什么一两分钟的情形基本都发生在爬取代理的过程中,是否意味着这个任务的代码有问题?
15:57:50 INFO Thread-2 the proxy already in list, skip
15:58:35 INFO Thread-10 {'https': '117.170.28.178:8123'} download 2111 bytes in 0.75 seconds(average in 1 tries), need 10, available count: 7
思考:我理解python的多线程调度机制是完成了一条指令后,就可以调用其他线程了,并不是一定要等着这个指令得到了预期的结果,那么如果我的代码写的有问题也不至于影响他的调度吧。这个线程没有进展又不将CPU的使用权让渡出来,GIL为什么不剥夺这个线程的运行时间,总不至于是在等待某个程序块或者函数运行完毕吧。
发现描述的问题主要是对sqlite的不当使用引起的,之前的设计是开启一个连接,直到完成代理池内所有代理的验证,并抓取到一定数量的代理后再关闭连接,且每当有代理信息的加入、修改、删除都去写数据文件,所以导致粗粒度的sqlite长时间处于加锁状态。
发现此问题后做了优化,起始新建连接读取完库存代理后马上关闭连接,之后所有的代理新增、更新、删除数据都暂存在类变量中,直到获取了所有需要的代理后,开启一个新连接,用executemany更新数据,然后关闭连接,完成预定任务,速度就上去了。
不过还是不能理解为什么原来的情形下,线程调度机制会允许那个因为数据库阻塞的线程一直占着资源,而不是及时切换呢?
所以你的线程是阻塞在写入数据库这一关, 既然你用到的是
sqlite
那么就再送你一道洪荒之力, 加速数据库写入操作:用到了三个加速
sqlite
写入速度的方法关闭磁盘同步
SQLite 事务
executemany 批量插入
PS: 另外, 如果内存宽裕, 完全可以把数据库文件扔到
tmpfs
目录, 这样就会大大消除磁盘I/O带来的影响(相当于直接在内存中写入)