
在软件开发中,我们经常需要对特定代码段或函数执行情况进行性能分析或行为追踪。python的上下文管理器(with语句)提供了一种优雅的方式来管理资源的生命周期或定义操作的范围。将函数调用监控与上下文管理器结合,可以实现按需、按范围的性能数据收集。
一个基本的函数调用监控系统通常包括以下几个核心组件:
以下是单线程环境下可行的初始实现:
import time
import threading
from dataclasses import dataclass
from collections import UserList # UserList for LocalList inheritance
@dataclass
class MonitorRecord:
function: str
time: float
class MonitorContext:
def __init__(self):
self._records: list[MonitorRecord] = []
def add_record(self, record: MonitorRecord) -> None:
self._records.append(record)
def __enter__(self) -> 'MonitorContext':
# 在进入上下文时,向全局处理器注册当前上下文
handlers.register(self)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
# 在退出上下文时,从全局处理器注销当前上下文
handlers.delete(self)
return
class MonitorHandlers:
def __init__(self):
# _handlers 列表存储所有注册的 MonitorContext
self._handlers: list[MonitorContext] = []
def register(self, handler: MonitorContext) -> None:
self._handlers.append(handler)
def delete(self, handler: MonitorContext) -> None:
self._handlers.remove(handler)
def add_record(self, record: MonitorRecord) -> None:
# 将记录分发给所有已注册的上下文
for h in self._handlers:
h.add_record(record)
# 全局的监控处理器实例
handlers = MonitorHandlers()
def monitor_decorator(f):
def wrapper(*args, **kwargs):
start = time.time()
result = f(*args, **kwargs) # 执行被装饰的函数
handlers.add_record(
MonitorRecord(
function=f.__name__,
time=time.time() - start,
)
)
return result
return wrapper
# 单线程示例
@monitor_decorator
def run_task():
time.sleep(0.1) # 模拟任务执行
if __name__ == '__main__':
print("--- 单线程测试 ---")
with MonitorContext() as m1:
run_task()
with MonitorContext() as m2:
run_task()
run_task()
print(f"m1 记录数: {len(m1._records)}") # 预期 3
print(f"m2 记录数: {len(m2._records)}") # 预期 2
# 单线程输出:
# m1 记录数: 3
# m2 记录数: 2上述代码在单线程环境下运行良好,可以正确地将函数调用记录到相应的嵌套上下文中。然而,当引入多线程时,问题便浮现了。由于handlers是一个全局变量,其内部的_handlers列表被所有线程共享。这意味着一个线程注册的上下文,可能会被其他线程的监控记录所填充,导致数据混乱和不准确的统计。例如,当多个线程同时创建MonitorContext时,它们都会将自己的上下文添加到同一个全局_handlers列表中,使得add_record方法会将记录分发给所有线程的上下文,而非仅限于当前线程或其父线程的上下文。
解决多线程问题的关键在于:
立即学习“Python免费学习笔记(深入)”;
Python的threading.local类是实现线程局部数据的理想工具。它为每个线程提供独立的存储空间,访问threading.local实例的属性时,实际上是访问当前线程特有的属性副本。
基于此,我们可以对MonitorHandlers类进行改造:
import time
import threading
from dataclasses import dataclass
from collections import UserList # UserList for LocalList inheritance
@dataclass
class MonitorRecord:
function: str
time: float
class MonitorContext:
def __init__(self):
self._records: list[MonitorRecord] = []
def add_record(self, record: MonitorRecord) -> None:
self._records.append(record)
def __enter__(self) -> 'MonitorContext':
handlers.register(self)
return self
def __exit__(self, exc_type, exc_val, exc_tb):
handlers.delete(self)
return
# 继承 threading.local 和 UserList,实现线程局部的列表
class LocalList(threading.local, UserList):
"""
一个线程局部列表,每个线程都会有自己的独立列表实例。
UserList 提供列表的完整接口。
"""
def __init__(self, initlist=None):
super().__init__() # 调用 UserList 的 __init__
if initlist is not None:
self.data = list(initlist) # 初始化内部数据
class MonitorHandlers:
def __init__(self):
# 用于保护 _mainhandlers 列表的锁
self._lock = threading.Lock()
# _mainhandlers 存储主线程的上下文,需要锁保护
with self._lock:
self._mainhandlers: list[MonitorContext] = []
# _handlers 存储非主线程的上下文,使用 LocalList 实现线程局部
self._handlers: list[MonitorContext] = LocalList()
def register(self, handler: MonitorContext) -> None:
# 根据当前线程是否为主线程,注册到不同的列表中
if threading.main_thread().ident == threading.get_ident():
with self._lock: # 主线程列表需要加锁
self._mainhandlers.append(handler)
else:
self._handlers.append(handler) # 非主线程直接添加到线程局部列表
def delete(self, handler: MonitorContext) -> None:
# 根据当前线程是否为主线程,从不同的列表中删除
if threading.main_thread().ident == threading.get_ident():
with self._lock: # 主线程列表需要加锁
self._mainhandlers.remove(handler)
else:
self._handlers.remove(handler) # 非主线程从线程局部列表删除
def add_record(self, record: MonitorRecord) -> None:
# 将记录分发给当前线程的所有上下文
for h in self._handlers:
h.add_record(record)
# 同时将记录分发给主线程的所有上下文(如果存在)
with self._lock: # 访问主线程列表需要加锁
for h in self._mainhandlers:
h.add_record(record)
# 全局的监控处理器实例
handlers = MonitorHandlers()
def monitor_decorator(f):
def wrapper(*args, **kwargs):
start = time.time()
result = f(*args, **kwargs)
handlers.add_record(
MonitorRecord(
function=f.__name__,
time=time.time() - start,
)
)
return result
return wrapper
# 多线程示例
@monitor_decorator
def run_threaded_task():
time.sleep(0.1) # 模拟任务执行
def nested_thread_context():
"""
在子线程中创建监控上下文并执行任务。
"""
with MonitorContext() as m_thread:
run_threaded_task()
print(f"子线程 {threading.get_ident()} 内部上下文记录数: {len(m_thread._records)}")
if __name__ == '__main__':
print("\n--- 多线程测试 ---")
threads = []
# 在主线程中创建监控上下文
with MonitorContext() as m_main:
for i in range(5): # 创建 5 个子线程
t = threading.Thread(target=nested_thread_context, name=f"Thread-{i}")
threads.append(t)
t.start()
for t in threads:
t.join() # 等待所有子线程完成
print(f"主线程上下文记录数: {len(m_main._records)}")
# 预期输出:
# 子线程 ... 内部上下文记录数: 1 (每个子线程的上下文只记录自己的任务)
# 主线程上下文记录数: 5 (主线程的上下文记录了所有子线程的任务)LocalList(threading.local, UserList):
_mainhandlers和线程锁:
register和delete方法:
add_record方法:
通过引入threading.local和threading.Lock,我们成功地将原有的单线程监控系统改造为多线程兼容的方案。新的MonitorHandlers类能够区分主线程和子线程的上下文,确保每个线程的数据独立性,同时允许子线程的监控数据汇总到主线程的上下文,为复杂的并发应用提供了可靠的函数行为追踪能力。理解并妥善处理多线程环境下的共享状态是构建健壮并发系统的关键。
以上就是Python上下文中的函数调用监控与多线程兼容性实现的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号