Python Pandas多线程操作DataFrame遇AssertionError: Gaps in blk ref_locs求解
关于
AssertionError: Gaps in blk ref_locs的解释与解决方法 错误含义
这个错误是Pandas内部的一致性检查失败导致的,根源在于Pandas的DataFrame/Series不是线程安全的。当多个线程同时对同一个DataFrame进行修改操作时,会破坏其内部维护的数据块(blk)的引用位置(ref_locs)的连续性——Pandas期望这些引用位置是连续无间隙的,但并发修改会让数据块的结构被打乱,触发断言报错。
从你的代码来看,两个线程同时在修改df1的不同行,即使看起来操作的是不同行,Pandas内部的内存管理结构还是会被并发操作干扰,尤其是当涉及到列的部分更新时,内部的数据块拆分/合并逻辑会在多线程下出问题。
解决方法
针对你的场景,这里有几个可行的解决方案:
1. 给临界区代码加线程锁
最直接的方式是用threading.Lock来保护所有修改df1的操作,确保同一时间只有一个线程能执行这些修改:
import pandas as pd import threading as th lock = th.Lock() # 定义全局锁 def foo(): t = True while True: with lock: # 进入临界区前获取锁,自动处理锁的释放 if t: df1.loc[1, ["A", "B", "C"]] = df2.loc[0, ["A", "B", "C"]] else: df1.loc[0, ["A", "B", "C"]] = df2.loc[1, ["A", "B", "C"]] t = not t df1 = pd.DataFrame([[1, 2, 3, "a"], [4, 5, 6, "b"]], columns = ["A", "B", "C", "D"]) df2 = pd.DataFrame([[7, 8, 9], [10, 11, 12]], columns = ["A", "B", "C"]) th.Thread(target = foo).start() th.Thread(target = foo).start()
2. 避免多线程共享同一个DataFrame
如果业务允许,可以让每个线程操作独立的DataFrame副本,这样就不会有并发冲突:
import pandas as pd import threading as th def foo(df_copy): t = True while True: if t: df_copy.loc[1, ["A", "B", "C"]] = df2.loc[0, ["A", "B", "C"]] else: df_copy.loc[0, ["A", "B", "C"]] = df2.loc[1, ["A", "B", "C"]] t = not t # 这里可以将处理后的副本结果同步回主进程,或者直接使用副本 df1 = pd.DataFrame([[1, 2, 3, "a"], [4, 5, 6, "b"]], columns = ["A", "B", "C", "D"]) df2 = pd.DataFrame([[7, 8, 9], [10, 11, 12]], columns = ["A", "B", "C"]) # 每个线程传入df1的独立副本 th.Thread(target = foo, args=(df1.copy(),)).start() th.Thread(target = foo, args=(df1.copy(),)).start()
3. 使用进程而非线程(规避GIL限制)
由于Python的GIL(全局解释器锁),CPU密集型的多线程操作其实并不能真正并行。如果你的数据操作是CPU密集型的,改用multiprocessing模块的进程会更合适,每个进程有独立的内存空间,不会共享DataFrame:
import pandas as pd from multiprocessing import Process def foo(df_copy): t = True while True: if t: df_copy.loc[1, ["A", "B", "C"]] = df2.loc[0, ["A", "B", "C"]] else: df_copy.loc[0, ["A", "B", "C"]] = df2.loc[1, ["A", "B", "C"]] t = not t # 处理后的结果可以通过队列等方式传递回主进程 df1 = pd.DataFrame([[1, 2, 3, "a"], [4, 5, 6, "b"]], columns = ["A", "B", "C", "D"]) df2 = pd.DataFrame([[7, 8, 9], [10, 11, 12]], columns = ["A", "B", "C"]) Process(target=foo, args=(df1.copy(),)).start() Process(target=foo, args=(df1.copy(),)).start()
额外提示
你提到删除那两行df1.loc赋值后其他操作能正常运行,这是因为那些操作没有触发Pandas内部数据块的修改,或者修改的方式没有破坏内部的引用结构,但只要涉及到并发修改DataFrame的核心数据,就存在触发这类错误的风险,所以务必保证对共享DataFrame的修改是线程安全的。
内容的提问来源于stack exchange,提问作者cspecial
相关产品推荐
相关产品推荐

