使用开源工具可视化多线程 Python 程序

VizTracer 追踪并发 Python 程序,以帮助进行日志记录、调试和性能分析。
84 位读者喜欢这个。
Open source voice control

Pixabay。由 Opensource.com 修改。CC BY-SA 4.0。

并发是现代编程的重要组成部分,因为我们有多个核心和许多需要协作的任务。然而,当并发程序不是顺序运行时,就更难理解它们。工程师在这些程序中识别错误和性能问题不像在单线程、单任务程序中那么容易。

使用 Python,您有多种并发选项。最常见的可能是使用 threading 模块的多线程、使用 subprocess 和 multiprocessing 模块的多进程,以及更新的带有 asyncio 模块的 async 语法。在 VizTracer 之前,缺乏分析使用这些技术的程序的工具。

VizTracer 是一款用于追踪和可视化 Python 程序的工具,它有助于日志记录、调试和性能分析。尽管它在单线程、单任务程序中也能很好地工作,但它在并发程序中的实用性使其独一无二。

尝试一个简单的任务

从一个简单的练习任务开始:找出数组中的整数是否为质数,并返回一个布尔数组。这是一个简单的解决方案

def is_prime(n):
    for i in range(2, n):
        if n % i == 0:
            return False
    return True

def get_prime_arr(arr):
    return [is_prime(elem) for elem in arr]

尝试在单线程中正常运行它,使用 VizTracer

if __name__ == "__main__":
    num_arr = [random.randint(100, 10000) for _ in range(6000)]
    get_prime_arr(num_arr)
viztracer my_program.py

调用堆栈报告表明它花费了大约 140 毫秒,其中大部分时间花费在 get_prime_arr 中。

它只是在数组中的元素上一次又一次地执行 is_prime 函数。

这是您所期望的,而且没有那么有趣(如果您了解 VizTracer)。

尝试一个多线程程序

尝试使用多线程程序来完成它

if __name__ == "__main__":
    num_arr = [random.randint(100, 10000) for i in range(2000)]
    thread1 = Thread(target=get_prime_arr, args=(num_arr,))
    thread2 = Thread(target=get_prime_arr, args=(num_arr,))
    thread3 = Thread(target=get_prime_arr, args=(num_arr,))

    thread1.start()
    thread2.start()
    thread3.start()

    thread1.join()
    thread2.join()
    thread3.join()

为了匹配单线程程序的工作负载,这里使用了 2,000 个元素的数组用于三个线程,模拟了三个线程共享任务的情况。

正如您如果熟悉 Python 的全局解释器锁 (GIL) 所预期的那样,它不会变得更快。由于开销,它花费了稍微超过 140 毫秒。但是,您可以观察到多个线程的并发性

当一个线程工作时(执行多个 is_prime 函数),另一个线程被冻结(一个 is_prime 函数);之后,它们切换了。这是由于 GIL 造成的,这也是 Python 没有真正多线程的原因。它可以实现并发,但不能实现并行。

尝试使用多进程

为了实现并行,最佳方法是使用 multiprocessing 库。这是另一个使用多进程的版本

if __name__ == "__main__":
    num_arr = [random.randint(100, 10000) for _ in range(2000)]
    
    p1 = Process(target=get_prime_arr, args=(num_arr,))
    p2 = Process(target=get_prime_arr, args=(num_arr,))
    p3 = Process(target=get_prime_arr, args=(num_arr,))

    p1.start()
    p2.start()
    p3.start()

    p1.join()
    p2.join()
    p3.join()

要使用 VizTracer 运行它,您需要一个额外的参数

viztracer --log_multiprocess my_program.py

整个程序在 50 毫秒多一点的时间内完成,实际任务在 50 毫秒标记之前完成。程序的速度大约提高了三倍。

为了与多线程版本进行比较,这是多进程版本

在没有 GIL 的情况下,多进程可以实现并行性,这意味着多个 is_prime 函数可以并行执行。

但是,Python 的多线程并非毫无用处。例如,对于计算密集型和 I/O 密集型程序,您可以使用 sleep 模拟 I/O 绑定任务

def io_task():
    time.sleep(0.01)

尝试在单线程、单任务程序中运行它

if __name__ == "__main__":
    for _ in range(3):
        io_task()

整个程序花费了大约 30 毫秒;没什么特别的。

现在使用多线程

if __name__ == "__main__":
    thread1 = Thread(target=io_task)
    thread2 = Thread(target=io_task)
    thread3 = Thread(target=io_task)

    thread1.start()
    thread2.start()
    thread3.start()

    thread1.join()
    thread2.join()
    thread3.join()

程序花费了 10 毫秒,并且很明显三个线程是如何并发工作并提高整体性能的。

尝试使用 asyncio

Python 正在尝试引入另一个有趣的功能,称为异步编程。您可以为此任务创建一个异步版本

import asyncio

async def io_task():
    await asyncio.sleep(0.01)

async def main():
    t1 = asyncio.create_task(io_task())
    t2 = asyncio.create_task(io_task())
    t3 = asyncio.create_task(io_task())

    await t1
    await t2
    await t3


if __name__ == "__main__":
    asyncio.run(main())

由于 asyncio 实际上是一个带有任务的单线程调度器,因此您可以直接在它上面使用 VizTracer

VizTracer with asyncio

<p class="rtecenter"><sup>(Tian Gao,<a href="https://open-source.net.cn/%3Ca%20href%3D"https://creativecommons.org/licenses/by-sa/4.0/" rel="ugc">https://creativecommons.org/licenses/by-sa/4.0/" target="_blank">CC BY-SA 4.0</a>)</sup></p>

它仍然花费了 10 毫秒,但显示的大多数函数都是底层结构,这可能不是用户感兴趣的。为了解决这个问题,您可以使用 --log_async 来分离实际任务

viztracer --log_async my_program.py

现在用户任务更加清晰。在大多数时间里,没有任务在运行(因为它唯一做的就是休眠)。这是有趣的部分

这显示了任务何时创建和执行。Task-1 是 main() 协程,并创建了其他任务。任务 2、3 和 4 执行了 io_tasksleep,然后等待唤醒。如图所示,任务之间没有重叠,因为这是一个单线程程序,VizTracer 以这种方式将其可视化,使其更易于理解。

为了使其更有趣,在任务中添加一个 time.sleep 调用来阻塞异步循环

async def io_task():
    time.sleep(0.01)
    await asyncio.sleep(0.01)

程序花费了更长的时间(40 毫秒),并且任务填补了异步调度器中的空白。

此功能对于诊断异步程序中的行为和性能问题非常有帮助。

了解 VizTracer 正在发生什么

使用 VizTracer,您可以在时间轴上看到程序中正在发生的事情,而不是从复杂的日志中想象它。这有助于您更好地理解并发程序。

VizTracer 是开源的,在 Apache 2.0 许可下发布,并支持所有常见的操作系统(Linux、macOS 和 Windows)。您可以在 VizTracer 的 GitHub 仓库中了解更多关于其功能的信息并访问其源代码。

接下来阅读什么
标签
User profile image.
VizTracer 作者

评论已关闭。

Creative Commons License本作品根据 Creative Commons Attribution-Share Alike 4.0 International License 许可获得许可。
© . All rights reserved.