MST

星途 面试题库

面试题:Python多线程与异步编程结合之基础运用

请描述在Python中,多线程与异步编程各自适用的场景。假设你要开发一个网络爬虫程序,需要快速抓取多个网页内容,你会如何结合多线程与异步编程来实现?请简要说明思路并给出关键代码片段。
28.2万 热度难度
编程语言Python

知识考点

AI 面试

面试题答案

一键面试

多线程适用场景

  1. I/O 密集型任务:如网络请求、文件读写等,线程在等待 I/O 操作完成时会释放 GIL(全局解释器锁),其他线程可以利用这段时间执行,提高 CPU 利用率。
  2. 需要并发执行多个相对独立任务,且任务间需要共享数据:多线程能方便地共享进程内的资源。

异步编程适用场景

  1. 高并发 I/O 密集型场景:特别是在有大量 I/O 操作且操作之间不需要太多同步的情况下,异步编程可以显著提高效率,比如处理大量网络请求。
  2. 对资源占用敏感:异步编程不需要创建大量线程,减少了内存等资源开销。

网络爬虫结合思路

  1. 使用多线程管理异步任务:主线程创建多个工作线程,每个工作线程负责管理一组异步的网络请求任务。这样可以利用多线程处理不同批次的网页抓取,而每个线程内使用异步编程提高单个线程内的请求效率。
  2. 异步请求网页:在每个工作线程内,使用异步库(如 aiohttp)发送网络请求获取网页内容,避免阻塞线程。

关键代码片段

import asyncio
import aiohttp
import threading


async def fetch(session, url):
    async with session.get(url) as response:
        return await response.text()


async def fetch_all(urls):
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        return results


def worker(urls):
    loop = asyncio.new_event_loop()
    asyncio.set_event_loop(loop)
    try:
        htmls = loop.run_until_complete(fetch_all(urls))
        print(htmls)
    finally:
        loop.close()


if __name__ == '__main__':
    all_urls = ["http://example.com", "http://example.org", "http://example.net"]
    num_threads = 2
    sub_lists = [all_urls[i::num_threads] for i in range(num_threads)]
    threads = []
    for sub_list in sub_lists:
        t = threading.Thread(target=worker, args=(sub_list,))
        threads.append(t)
        t.start()
    for t in threads:
        t.join()

在上述代码中,fetch 函数是一个异步函数,负责单个网页的抓取。fetch_all 函数用于批量处理多个 URL 的抓取。worker 函数在每个线程内创建一个新的事件循环来运行异步任务。在 main 部分,将 URL 列表分割成多个子列表,为每个子列表创建一个线程来执行 worker 函数,从而结合多线程和异步编程实现网页抓取。