Python 2.7线程传递函数句柄异常:重复调用同一实例方法排查
问题原因:闭包的延迟绑定陷阱
你遇到的这个问题是Python里闭包延迟绑定的经典坑。在循环里定义lambda时,里面引用的fn变量并没有在迭代时就绑定到当前的foo.bar,而是要等到lambda实际执行时才会去查找fn的当前值。当你的线程开始运行时,循环已经跑完了,fn已经指向了最后一个Foo实例的bar方法,所以两个线程都调用了第二个对象的方法。
快速修复你的现有代码
要解决这个问题,只需要在创建lambda时,把fn和args作为默认参数传递进去,强制在循环迭代时就绑定当前的值:
for foo in foos: args = "some stuff" fn = foo.bar Log.debug("before async: " + str(foo)) # 把fn和args作为默认参数传入lambda,立即绑定当前值 task = AsyncTask(lambda fn=fn, args=args: [ Log.debug("in async: " + str(fn)), results.append(fn(args)) ]) tasks.append(task) task.start()
这样修改后,每个lambda都会持有当前循环迭代时的fn和args副本,就不会出现都指向最后一个实例的问题了。
更Pythonic的实现方式
其实你完全不需要自己实现AsyncTask类,Python 2.7可以使用第三方库futures(对应Python3标准库的concurrent.futures),它提供了更简洁、健壮的线程池实现,能省去很多手动管理线程的麻烦。
步骤1:安装futures库
pip install futures
步骤2:用ThreadPoolExecutor重写代码
from concurrent.futures import ThreadPoolExecutor import time # 假设Log是你的日志工具 class Foo: def __init__(self, id): self.id = id def bar(self, args): time.sleep(10) Log.debug("bar() called in object %s" % self.id) return f"result from {self.id}" if __name__ == "__main__": foos = [Foo("1"), Foo("2")] args = "some stuff" # 创建线程池,指定线程数 with ThreadPoolExecutor(max_workers=2) as executor: # 提交任务到线程池 futures = [executor.submit(foo.bar, args) for foo in foos] # 获取所有结果 results = [future.result() for future in futures] Log.debug(f"All results: {results}")
这种方式的优势:
- 不需要手动管理线程的创建、启动和
join,with语句会自动处理资源释放 - 可以方便地获取任务返回结果,无需自己维护
results列表 - 内置异常处理机制,任务抛出异常时可通过
future.exception()获取 - 支持更灵活的任务提交和结果处理方式(比如
map方法)
如果你坚持自己实现异步任务类,也可以优化AsyncTask,支持直接传递任务参数,避免lambda闭包问题:
import threading class AsyncTask: def __init__(self, task, args=(), kwargs=None, name="async-task", callback=None): self.task = task self.args = args self.kwargs = kwargs or {} self.callback = callback self.t = None if self.task is not None: # 直接绑定任务和参数,避免lambda闭包问题 def run_task(): result = self.task(*self.args, **self.kwargs) if self.callback: self.callback(result) self.t = threading.Thread(target=run_task, name=name) def start(self): if self.t is not None: self.t.start() else: Log.warn("Can't start async task: thread is None") def join(self): if self.t is not None: self.t.join() else: Log.warn("Can't join async task: thread is None")
调用时直接传参数即可:
for foo in foos: args = "some stuff" Log.debug("before async: " + str(foo)) # 直接传递任务和参数,不需要lambda task = AsyncTask(foo.bar, args=(args,), callback=lambda res: results.append(res)) tasks.append(task) task.start()
这样也能避免闭包问题,代码逻辑更清晰。
内容的提问来源于stack exchange,提问作者JohannB
相关产品推荐
相关产品推荐

