MST

星途 面试题库

面试题:Python线程优雅退出在复杂场景的应用

假设你正在开发一个多线程的网络爬虫程序,每个线程负责抓取一个网页。在爬虫运行过程中,需要能够随时优雅地停止所有线程。同时,每个线程在停止前要确保已经处理完当前正在抓取的网页并保存相关数据。请描述实现思路并给出关键代码片段。
49.1万 热度难度
编程语言Python

知识考点

AI 面试

面试题答案

一键面试

实现思路

  1. 标志位控制:使用一个共享的标志位来通知所有线程需要停止。
  2. 线程安全:确保标志位的访问是线程安全的,例如使用std::mutex
  3. 任务完成确认:每个线程在循环中检查标志位,在退出前确保当前网页抓取和数据保存完成。

关键代码片段(以C++为例)

#include <iostream>
#include <thread>
#include <vector>
#include <mutex>
#include <atomic>

std::atomic<bool> stopFlag(false);
std::mutex mtx;

// 模拟抓取网页并保存数据的函数
void crawlWebPage(int pageId) {
    while (!stopFlag) {
        // 模拟抓取网页
        std::cout << "Thread " << std::this_thread::get_id() << " is crawling page " << pageId << std::endl;
        // 模拟保存数据
        std::cout << "Thread " << std::this_thread::get_id() << " has saved data for page " << pageId << std::endl;
        // 适当休眠以模拟实际工作
        std::this_thread::sleep_for(std::chrono::seconds(1));
    }
    // 停止前确保处理完当前网页
    std::cout << "Thread " << std::this_thread::get_id() << " is finishing up page " << pageId << std::endl;
}

int main() {
    const int numThreads = 3;
    std::vector<std::thread> threads;

    // 创建并启动线程
    for (int i = 0; i < numThreads; ++i) {
        threads.emplace_back(crawlWebPage, i);
    }

    // 主线程等待一段时间,模拟其他工作
    std::this_thread::sleep_for(std::chrono::seconds(5));

    // 设置停止标志
    stopFlag = true;

    // 等待所有线程结束
    for (auto& thread : threads) {
        thread.join();
    }

    std::cout << "All threads have stopped." << std::endl;

    return 0;
}

在上述代码中:

  • std::atomic<bool> stopFlag 用于控制线程停止,原子类型确保线程安全访问。
  • crawlWebPage 函数模拟了网页抓取和数据保存工作,并在循环中检查 stopFlag
  • 主线程等待一段时间后设置 stopFlag,然后等待所有线程结束。

不同编程语言实现方式有所不同,但核心思路类似,如Java可使用volatile关键字和Thread.join()方法等。例如Java代码示例:

public class WebCrawler {
    private static volatile boolean stopFlag = false;

    // 模拟抓取网页并保存数据的方法
    public static void crawlWebPage(int pageId) {
        while (!stopFlag) {
            // 模拟抓取网页
            System.out.println(Thread.currentThread().getName() + " is crawling page " + pageId);
            // 模拟保存数据
            System.out.println(Thread.currentThread().getName() + " has saved data for page " + pageId);
            // 适当休眠以模拟实际工作
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 停止前确保处理完当前网页
        System.out.println(Thread.currentThread().getName() + " is finishing up page " + pageId);
    }

    public static void main(String[] args) {
        final int numThreads = 3;
        Thread[] threads = new Thread[numThreads];

        // 创建并启动线程
        for (int i = 0; i < numThreads; ++i) {
            threads[i] = new Thread(() -> crawlWebPage(i));
            threads[i].start();
        }

        // 主线程等待一段时间,模拟其他工作
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        // 设置停止标志
        stopFlag = true;

        // 等待所有线程结束
        for (Thread thread : threads) {
            try {
                thread.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        System.out.println("All threads have stopped.");
    }
}

在Java代码中,volatile关键字保证stopFlag的可见性,每个线程能及时获取其最新值。Thread.join()方法用于等待线程执行完毕。