背景:运行一个爬虫,开了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帶來的影響(相當於直接在記憶體中寫入)