python3适当的中断多线程的方法

时间:2018-02-19 04:48:45

标签: python multithreading python-3.x

我正在编写一个多线程的Web爬虫,每个线程的例程函数基本上是一个无限循环,并且有一个共享字典来存储那些已经访问过的url,并且我使用线程锁来同步。

我可能想用ctrl + c中断它有时候,我在线程运行函数中添加一个try catch来捕获键盘中断,之后我可能想做一些最终任务,例如将数据转储到数据库或pkl文件。

问题在于,每次我打断它时,它都无法进入捕获过程,有时它只是卡在那里,有时它仍然继续运行。

那么在多线程编程中处理异常/中断的最佳做法是什么?

我把我的代码如下:

from bs4 import BeautifulSoup
import requests
import threading
import queue
import pickle
import os
from concurrent.futures import ThreadPoolExecutor

worker_num = 8
q = queue.Queue()
lock = threading.Lock()

if os.path.exists('./checked.pkl'):
    with open('./checked.pkl', 'rb') as f:
        checked = pickle.load(f)
else:
    checked = set()


def get_links(url):
    # do sth....


def run():
    print(threading.current_thread())
    try:
        while True:
            next_url = q.get()
            links = get_links(next_url)
            lock.acquire()
            for link in links:
                if link not in checked:
                    q.put(link)
            print(len(checked))
            lock.release()
    except Exception as e:
        print(e)
        print('interrupt')
        lock.acquire()
        with open('./checked.pkl', 'wb') as f:
            pickle.dump(checked, f)
        lock.release()


if __name__ == '__main__':
    q.put(start_url)
    with ThreadPoolExecutor(worker_num) as executor:
        for _ in range(worker_num):
            executor.submit(run)

1 个答案:

答案 0 :(得分:2)

KeyboardInterrupt总是在主线程中引发。你无法在另一个线程中处理它。

你应该在每个成功的工作块之后检查你的状态(它不必在每个URL之后,但它也可能是)。这将为您提供状态更新,即使您的进程因其他原因崩溃(一些不可理解的原因 - 如段错误或整个主机崩溃)。您还应该以原子方式编写检查点,以便在更新中期崩溃时,您不会以腐败,无法使用的状态结束。 (由于其不安全性,脆弱性和跨语言挑战,您也不应该使用pickle作为检查点状态格式。)

在正常操作期间安全地定期写入状态更新后,可以通过将工作线程转换为守护程序线程并将KeyboardInterrupt处理添加到主线程来使爬网程序可中断。在这一点上,如果工作线程被杀死而没有机会进行清理(如果你让它们成为守护程序线程并让主线程退出就会发生),因为它们最近会检查它们的状态,这无关紧要。

此外,您应该考虑使用现有的抓取工具,例如Scrapy,而不是自己滚动。