异步:Python的asyncio/greenlet,Java的CompletableFuture

·3822·8 分钟·
AI摘要: 本文介绍了Python和Java中实现异步编程的几种方法,包括Python中的threading、asyncio和greenlet,以及Java中的CompletableFuture。文章详细比较了这些工具在控制权、调度方式和生态支持方面的差异,并提供了相应的代码示例来说明它们的使用方法。

Python:threading 和 asyncio 实现异步

使用 threading 模拟异步

import time
import threading


def simulate_task(task_id):
    print(f"Task {task_id} started.")
    time.sleep(2)  # 模拟耗时操作
    print(f"Task {task_id} finished.")


start_time = time.time()


threads = []
for i in range(150):  # 150个任务
    thread = threading.Thread(target=simulate_task, args=(i,))
    threads.append(thread)
    thread.start()


for thread in threads:
    thread.join()


end_time = time.time()
print(f"Total time taken with threading: {end_time - start_time} seconds")

这是比较常规的实现,虽然Python有全局GIL锁,但是在IO密集型任务中问题不算大,只是影响CPU密集型中对多核CPU利用率。

在IO密集型中的多线程实现中,如果线程过多,也会由于频繁的上下文切换而拖累性能。

使用 asyncio 实现异步

import time
import asyncio


async def simulate_task(task_id):
    print(f"Task {task_id} started.")
    await asyncio.sleep(2)  # 模拟耗时操作
    print(f"Task {task_id} finished.")


start_time = time.time()


async def main():
    tasks = []
    for i in range(150):  # 150个任务
        tasks.append(simulate_task(i))
    await asyncio.gather(*tasks)


asyncio.run(main())


end_time = time.time()
print(f"Total time taken with asyncio: {end_time - start_time} seconds")

Asyncio基于协程和事件循环来实现。由于协程的特性,多个协程也不过是在单个线程内部切换,没有上下文切换的损耗,所以想搞多少就搞多少协程,充分压榨CPU。事件循环主要是为了管理多个协程之间的切换。对比上面的Threading版本,速度快0.02s左右。

异步任务嵌套

import asyncio

async def task1():
    print("Task 1 start")
    await asyncio.sleep(1)
    print("Task 1 end")

async def task2():
    print("Task 2 start")
    await task1()  # 调用另一个异步函数
    print("Task 2 end")

async def other_task():
    for i in range(3):
        print(f"Other task is running {i+1}")
        await asyncio.sleep(0.5)

async def main():
    # 同时运行多个任务
    await asyncio.gather(
        task2(),
        other_task(),
    )

asyncio.run(main())

协程阻塞住之后,会自动让出cpu去执行别的任务。就如上面,在task1卡住之后,事件循环会去运行other_task。(不会执行task2的,这个依赖task1的执行,卡在await task1() 这行)


Python:greenlet 协程实现

from greenlet import greenlet


def task_1():
    print("Task 1: Start")
    g2.switch()  # 切换到 task_2
    print("Task 1: End")

def task_2():
    print("Task 2: Start")
    g1.switch()  # 切换回 task_1
    print("Task 2: End")


g1 = greenlet(task_1)
g2 = greenlet(task_2)


g1.switch()  # 启动任务 1

greenlet也是基于协程的异步工具,不过有了asyncio之后,用的不多了。

对比 greenletasyncio

  • 控制权greenlet 的控制权是显式的,开发者需要在代码中手动指定任务切换的时机。而 asyncio 通过事件循环和 await 自动管理任务切换。
  • 调度方式greenlet 使用协作式调度,需要手动切换任务。asyncio 则是事件驱动的自动调度,适用于 I/O 密集型任务,且简洁高效。
  • 生态和支持asyncio 是 Python 标准库的一部分,得到了更广泛的支持。greenlet 通常在性能要求较高的底层实现中使用。

Java:使用 CompletableFuture 实现异步

import java.util.concurrent.CompletableFuture;

Blog class AsyncExample {
    Blog static CompletableFuture<Void> task1() {
        return CompletableFuture.runAsync(() -> {
            try {
                System.out.println("Task 1 started");
                Thread.sleep(2000);  // 模拟耗时操作
                System.out.println("Task 1 finished");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
    }

    Blog static CompletableFuture<Void> task2() {
        return task1().thenRun(() -> {  // 调用另一个异步函数
            System.out.println("Task 2 started");
            System.out.println("Task 2 finished");
        });
    }

    Blog static void main(String[] args) {
        task2().join();  // 启动任务 2 并等待完成
    }
}

CompletableFuture 与协程对比CompletableFuture 并不是协程,它使用线程池和回调机制来实现异步操作,每个异步任务通常由独立线程执行。Java 中的异步通常通过 ExecutorService(如 ForkJoinPool)来管理任务,而不是使用协程。


总结:

  • Pythonasyncio 是标准的异步编程方式,基于协程和事件循环,适用于 I/O 密集型任务。greenlet 提供了一种低级的协程实现,控制权由开发者显式管理。两者在生态和支持上有不同的使用场景。
  • JavaCompletableFuture 不是协程,而是基于线程池和回调机制的异步实现,常用于处理异步任务。与 Python 的协程不同,Java 更倾向于通过线程池管理并发任务。
Kaggle学习赛初探