MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Python threading 模块其他实用函数介绍

2021-09-242.1k 阅读

threading.active_count()

在Python的threading模块中,active_count()函数用于返回当前处于活动状态的线程对象的数量。这个数量包括主线程以及所有启动的子线程。

原理剖析

Python的线程管理机制维护了一个内部的数据结构,用于追踪所有当前活动的线程。active_count()函数实际上就是从这个数据结构中获取活动线程的数量。每当一个线程启动时,这个数据结构会相应更新,增加活动线程计数;而当线程结束时,计数会减少。

代码示例

import threading
import time


def worker():
    print(f"子线程 {threading.current_thread().name} 开始")
    time.sleep(1)
    print(f"子线程 {threading.current_thread().name} 结束")


threads = []
for i in range(3):
    t = threading.Thread(target=worker, name=f"Thread-{i}")
    threads.append(t)
    t.start()

print(f"活动线程数: {threading.active_count()}")
for t in threads:
    t.join()
print(f"所有线程结束后活动线程数: {threading.active_count()}")

在上述代码中,我们创建并启动了3个子线程。在启动线程后,通过threading.active_count()获取活动线程数,此时会输出4,因为包括主线程和3个子线程。当所有子线程执行完毕并通过join()方法等待它们结束后,再次获取活动线程数,此时只会输出1,即主线程。

threading.enumerate()

threading.enumerate()函数返回一个包含当前所有活动线程对象的列表。这个列表包括主线程以及所有启动的子线程。

原理剖析

同样依赖于Python线程管理的内部数据结构,enumerate()函数从该结构中获取所有当前处于活动状态的线程对象,并将它们以列表的形式返回。这样开发者可以方便地对每个活动线程进行进一步操作,比如查看线程状态、获取线程名称等。

代码示例

import threading
import time


def worker():
    print(f"子线程 {threading.current_thread().name} 开始")
    time.sleep(1)
    print(f"子线程 {threading.current_thread().name} 结束")


threads = []
for i in range(2):
    t = threading.Thread(target=worker, name=f"Thread-{i}")
    threads.append(t)
    t.start()

active_threads = threading.enumerate()
print("当前活动线程:")
for thread in active_threads:
    print(f"线程名: {thread.name}, 线程是否存活: {thread.is_alive()}")

for t in threads:
    t.join()

在这个代码示例中,我们创建并启动了2个子线程。然后通过threading.enumerate()获取所有活动线程,并遍历输出每个线程的名称以及是否存活的状态。在所有子线程通过join()方法结束后,列表中的线程状态会相应改变。

threading.current_thread()

threading.current_thread()函数返回当前执行代码所在的线程对象。如果在主线程中调用,将返回主线程对象;如果在子线程中调用,则返回对应的子线程对象。

原理剖析

Python的线程执行环境会为每个线程维护一个上下文,current_thread()函数就是从当前线程的上下文中获取对应的线程对象。这样开发者可以在不同线程的代码中方便地操作当前线程的属性,比如设置线程名称、获取线程标识符等。

代码示例

import threading
import time


def worker():
    current_thread = threading.current_thread()
    print(f"子线程 {current_thread.name} 开始")
    time.sleep(1)
    print(f"子线程 {current_thread.name} 结束")


t = threading.Thread(target=worker, name="WorkerThread")
t.start()

main_thread = threading.current_thread()
print(f"主线程 {main_thread.name} 中调用")
t.join()

在上述代码中,在worker函数内部通过threading.current_thread()获取当前子线程对象,并输出子线程的名称。在主线程中也通过current_thread()获取主线程对象并输出名称。

threading.main_thread()

threading.main_thread()函数返回主线程对象。在Python程序启动时,会创建一个主线程,所有其他线程都是从主线程派生出来的。

原理剖析

Python在程序启动时就创建并初始化了主线程对象,并将其保存在一个全局可访问的位置。main_thread()函数就是从这个全局位置获取主线程对象,方便开发者在程序的任何地方对主线程进行操作,比如判断当前线程是否为主线程等。

代码示例

import threading
import time


def worker():
    current_thread = threading.current_thread()
    main_thread = threading.main_thread()
    if current_thread is main_thread:
        print("当前是主线程")
    else:
        print(f"当前是子线程 {current_thread.name}")


t = threading.Thread(target=worker, name="WorkerThread")
t.start()
worker()
t.join()

在这个代码示例中,worker函数中首先获取当前线程对象和主线程对象,然后通过比较判断当前线程是否为主线程。在主线程中调用worker函数和通过子线程调用worker函数会有不同的输出结果。

threading.settrace(func)

threading.settrace(func)函数用于为所有线程设置一个跟踪函数。这个跟踪函数会在每个线程的每个代码块执行前被调用。

原理剖析

Python的线程执行机制在执行字节码指令时,会检查是否设置了跟踪函数。如果设置了,在进入每个代码块(可以理解为一段连续执行的字节码指令)之前,会暂停当前线程的正常执行流程,调用跟踪函数,并将相关的执行信息传递给跟踪函数。跟踪函数可以根据这些信息进行日志记录、性能分析等操作。

代码示例

import threading


def trace_func(frame, event, arg):
    if event == 'call':
        print(f"线程 {threading.current_thread().name} 进入函数 {frame.f_code.co_name}")
    return trace_func


def worker():
    print("子线程工作开始")


threading.settrace(trace_func)
t = threading.Thread(target=worker, name="WorkerThread")
t.start()
t.join()

在上述代码中,我们定义了一个跟踪函数trace_func,它会在每个函数调用时输出线程名称和函数名称。通过threading.settrace(trace_func)为所有线程设置这个跟踪函数。当启动子线程并执行worker函数时,跟踪函数会被调用并输出相应信息。

threading.setprofile(func)

threading.setprofile(func)函数用于为所有线程设置一个性能分析函数。这个函数会在每个线程的每个代码块执行前后被调用。

原理剖析

settrace类似,Python线程执行机制在字节码指令执行过程中,会在进入和离开每个代码块时检查是否设置了性能分析函数。如果设置了,会分别在进入和离开代码块时调用该函数,并传递相关的执行信息,比如当前线程对象、代码块相关的帧对象等。开发者可以利用这些信息来统计函数执行时间、分析代码性能瓶颈等。

代码示例

import threading
import time


def profile_func(frame, event, arg):
    if event == 'call':
        print(f"线程 {threading.current_thread().name} 进入函数 {frame.f_code.co_name}")
        start_time = time.time()
        frame.f_locals['_start_time'] = start_time
    elif event == 'return':
        start_time = frame.f_locals.get('_start_time')
        if start_time is not None:
            elapsed_time = time.time() - start_time
            print(f"线程 {threading.current_thread().name} 离开函数 {frame.f_code.co_name}, 耗时 {elapsed_time} 秒")


def worker():
    time.sleep(0.5)


threading.setprofile(profile_func)
t = threading.Thread(target=worker, name="WorkerThread")
t.start()
t.join()

在这个代码示例中,profile_func函数在函数调用时记录开始时间,并在函数返回时计算并输出函数执行的耗时。通过threading.setprofile(profile_func)为所有线程设置该性能分析函数,从而可以对worker函数的执行性能进行分析。

threading.Timer

threading.Timerthreading模块中的一个类,用于在指定的延迟时间后执行一个函数。它本质上是一个线程,在延迟时间到达后,会启动并执行指定的函数。

原理剖析

threading.Timer类继承自threading.Thread类。当创建一个Timer对象时,会设置延迟时间和要执行的函数及参数。在调用start()方法后,线程开始运行,它会先睡眠指定的延迟时间,时间一到,就调用指定的函数。

代码示例

import threading


def delayed_function():
    print("延迟执行的函数被调用")


timer = threading.Timer(3, delayed_function)
print("启动定时器")
timer.start()
print("定时器已启动,主线程继续执行")

在上述代码中,我们创建了一个Timer对象,设置延迟3秒后执行delayed_function函数。调用start()方法启动定时器后,主线程会继续执行后续代码,3秒后delayed_function函数会被调用并输出相应信息。

threading.local()

threading.local()函数返回一个线程局部对象。这个对象可以在不同线程中拥有独立的数据副本,每个线程对这个对象的属性操作都不会影响其他线程。

原理剖析

threading.local对象内部使用了Python的线程本地存储(TLS)机制。当一个线程访问threading.local对象的属性时,实际上是在该线程的本地存储空间中进行操作。不同线程的本地存储空间是相互隔离的,这就保证了每个线程可以独立地使用和修改threading.local对象的属性,而不会产生数据竞争。

代码示例

import threading


local_data = threading.local()


def worker():
    local_data.value = threading.current_thread().name
    print(f"线程 {local_data.value} 设置了本地数据")
    print(f"线程 {local_data.value} 获取本地数据: {local_data.value}")


threads = []
for i in range(3):
    t = threading.Thread(target=worker)
    threads.append(t)
    t.start()

for t in threads:
    t.join()

在这个代码示例中,我们创建了一个threading.local对象local_data。在每个子线程中,我们为local_data对象设置一个与线程名称相关的值,并获取这个值。由于每个线程都有自己独立的local_data副本,所以不同线程设置和获取的值不会相互干扰。

通过对以上threading模块中这些实用函数和类的深入了解,开发者可以更加灵活和高效地利用多线程技术来开发Python程序,解决诸如性能优化、并发控制等实际问题。无论是简单的线程状态查询,还是复杂的线程跟踪和性能分析,这些工具都提供了强大的支持。