MST

星途 面试题库

面试题:Python异步编程之中等难度:协程与线程的区别

在Python异步编程中,协程和线程都能实现并发操作。请详细阐述协程与线程的区别,包括但不限于资源开销、调度方式、适用场景方面,并举例说明在哪些Python异步编程场景下使用协程会比线程更合适。
21.1万 热度难度
编程语言Python

知识考点

AI 面试

面试题答案

一键面试

资源开销

  • 线程:线程的创建、销毁和切换开销较大。每个线程都需要占用一定的内存空间来维护其栈空间,一般情况下,线程栈的大小在数MB级别。
  • 协程:协程的创建和切换开销极小。协程是在用户态下进行调度,不需要像线程那样进行上下文切换,其栈空间也相对较小,通常只有几KB。

调度方式

  • 线程:由操作系统内核进行调度。操作系统根据一定的调度算法(如时间片轮转等),在多个线程之间分配CPU时间。线程的调度是抢占式的,即当一个线程的时间片用完或者有更高优先级的线程出现时,当前线程会被暂停执行。
  • 协程:由程序员在代码中通过yield、await等关键字手动控制调度。协程的调度是非抢占式的,只有当一个协程主动让出控制权时,其他协程才能获得执行机会。

适用场景

  • 线程:适用于I/O密集型任务,但在CPU密集型任务中,由于线程的频繁切换会带来较大开销,性能反而会下降。例如,在进行网络请求、文件读写等I/O操作时,线程可以在等待I/O完成的过程中让出CPU,让其他线程有机会执行。
  • 协程:特别适用于高并发、I/O密集型任务。由于其开销小、调度灵活的特点,在处理大量并发连接(如网络爬虫、Web服务器等)时,能够显著提高程序的性能和效率。

协程更合适的Python异步编程场景举例

  1. 网络爬虫:在爬取大量网页时,需要频繁进行网络请求,这是典型的I/O密集型任务。使用协程可以在等待网络响应时,快速切换到其他请求,大大提高爬取效率。例如使用aiohttp库进行异步网络请求:
import asyncio
import aiohttp


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


async def main():
    urls = ['http://example.com', 'http://example.org', 'http://example.net']
    async with aiohttp.ClientSession() as session:
        tasks = [fetch(session, url) for url in urls]
        results = await asyncio.gather(*tasks)
        print(results)


if __name__ == '__main__':
    asyncio.run(main())
  1. Web服务器:处理大量客户端连接时,每个连接的处理过程可能包含I/O操作(如读取请求数据、写入响应数据等)。使用协程可以轻松应对高并发连接,提高服务器的吞吐量。例如Tornado框架,它基于协程实现了高性能的Web服务器。
import tornado.ioloop
import tornado.web


class MainHandler(tornado.web.RequestHandler):
    async def get(self):
        await asyncio.sleep(1)  # 模拟I/O操作
        self.write("Hello, world")


def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
    ])


if __name__ == "__main__":
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()

在上述例子中,协程能够在I/O操作时让出执行权,使得其他任务可以继续执行,从而在高并发场景下提高程序的整体性能,而线程由于其调度方式和资源开销的限制,在这种场景下不如协程高效。