多线程 - 为何python子线程会等待很长时间

 能然然520 发布于 2022-10-29 06:01

背景:运行一个爬虫,开了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为什么不剥夺这个线程的运行时间,总不至于是在等待某个程序块或者函数运行完毕吧。

2 个回答
  • 所以你的线程是阻塞在写入数据库这一关, 既然你用到的是sqlite那么就再送你一道洪荒之力, 加速数据库写入操作:

    import sqlite3
    
    ...
    conn = sqlite3.connect('xxx.db')
    cur = conn.cursor()
    cur.execute("CREATE TABLE xxx")  # 建个表
    cur.execute("PRAGMA synchronous = OFF")  # 关闭磁盘同步
    cur.execute("BEGIN TRANSACTION")  # 开始事务处理
    cur.executemany("INSERT INTO names VALUES (?,?)", lst)  # 批量插入爬到的数据
    conn.commit()
    conn.close()
    ...
    

    用到了三个加速sqlite写入速度的方法

    1. 关闭磁盘同步

    2. SQLite 事务

    3. executemany 批量插入

    PS: 另外, 如果内存宽裕, 完全可以把数据库文件扔到tmpfs目录, 这样就会大大消除磁盘I/O带来的影响(相当于直接在内存中写入)

    2022-10-30 12:11 回答
  • 发现描述的问题主要是对sqlite的不当使用引起的,之前的设计是开启一个连接,直到完成代理池内所有代理的验证,并抓取到一定数量的代理后再关闭连接,且每当有代理信息的加入、修改、删除都去写数据文件,所以导致粗粒度的sqlite长时间处于加锁状态。

    发现此问题后做了优化,起始新建连接读取完库存代理后马上关闭连接,之后所有的代理新增、更新、删除数据都暂存在类变量中,直到获取了所有需要的代理后,开启一个新连接,用executemany更新数据,然后关闭连接,完成预定任务,速度就上去了。

    不过还是不能理解为什么原来的情形下,线程调度机制会允许那个因为数据库阻塞的线程一直占着资源,而不是及时切换呢?

    2022-10-30 12:12 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有