使用multiprocessing.pool绘图出现空白问题求助
问题原因
- Matplotlib的
Figure和Axes对象不能跨进程共享。多进程模式下,子进程会复制主进程的fig对象,子进程中对ax的绘图操作只作用于副本,主进程的原始fig完全没变化,所以最终保存的只有主进程添加的suptitle,子进程画的内容都丢失了。 - 你的代码还有几个小问题:
do_something里调用plot_data时没传ax参数,会默认用子进程的当前轴,和主进程的fig无关。- 代码里
display_cols和position变量未定义,会触发报错。 actuals和predicted是列表集合,但你在plot_data里直接用了未绑定的actual和predicted,没有对应到每个col的具体数据。
修复方案
方案1:改用单进程批量绘图(最简单可靠)
Matplotlib本身对多进程支持有限,单进程绘图完全能满足大部分场景需求,除非你的distribution_intersection_area计算极慢,否则没必要用多进程。修改后的代码:
import matplotlib.pyplot as plt import numpy as np def distribution_intersection_area(actual, predicted): # 补全你的实际计算逻辑,这里用示例数据占位 x = np.linspace(0, 60, 100) kde1_x = np.exp(-(x - np.mean(actual))**2 / (2*np.std(actual)**2)) kde2_x = np.exp(-(x - np.mean(predicted))**2 / (2*np.std(predicted)**2)) idx = np.argmin(np.abs(kde1_x - kde2_x)) area = np.trapz(np.minimum(kde1_x, kde2_x), x) return area, kde1_x, kde2_x, idx, x def plot_data(actual, predicted, ax): if ax is None: ax = plt.gca() area, kde1_x, kde2_x, idx, x = distribution_intersection_area(actual, predicted) ax.plot(x, kde1_x, color='dodgerblue',label='original', linewidth=2) ax.plot(x, kde2_x, color='orangered', label='forecasted', linewidth=2) ax.fill_between(x, np.minimum(kde1_x, kde2_x), 0, color='lime', alpha=0.3, label='intersection') ax.plot(x[idx], kde2_x[idx], 'ko') handles, labels = ax.get_legend_handles_labels() labels[2] += f': {area * 100:.1f}%' ax.legend(handles, labels) def do_something(col, k, rows, cols, fig, actual, predicted): ax = fig.add_subplot(rows, cols, k+1) # 子图位置从1开始计数 plot_data(actual, predicted, ax) annot = f'Plot for {col}' ax.set_title(annot) # 配置参数 rows = 10 cols = 3 # 对应loop_list的长度 fig = plt.figure(figsize=(30, 4 * rows)) fig.subplots_adjust(hspace=0.3, wspace=0.2) # 替换为你的真实数据,这里示例为每个col生成一组数据 loop_list = ['col1','col2','col3'] actuals = [np.random.randn(100) + i for i in range(3)] predicted = [np.random.randn(100) + i*2 for i in range(3)] # 单进程循环绘图 for k, col in enumerate(loop_list): do_something(col, k, rows, cols, fig, actuals[k], predicted[k]) # 保存和显示 plt.suptitle('my calculation status', y=0.94, fontsize=18) plt.savefig('./outputs/test.jpg', dpi=150) plt.show()
方案2:多进程计算数据,主进程绘图(适合计算密集场景)
如果distribution_intersection_area计算非常耗时,可把计算部分放到子进程,返回绘图所需数据后由主进程统一绘图:
import matplotlib.pyplot as plt import numpy as np from multiprocessing import Pool def distribution_intersection_area(actual, predicted): x = np.linspace(0, 60, 100) kde1_x = np.exp(-(x - np.mean(actual))**2 / (2*np.std(actual)**2)) kde2_x = np.exp(-(x - np.mean(predicted))**2 / (2*np.std(predicted)**2)) idx = np.argmin(np.abs(kde1_x - kde2_x)) area = np.trapz(np.minimum(kde1_x, kde2_x), x) return area, kde1_x, kde2_x, idx, x # 子进程只做计算,返回绘图所需数据 def calculate_plot_data(actual, predicted): return distribution_intersection_area(actual, predicted) def plot_data_from_result(ax, data, col): area, kde1_x, kde2_x, idx, x = data ax.plot(x, kde1_x, color='dodgerblue',label='original', linewidth=2) ax.plot(x, kde2_x, color='orangered', label='forecasted', linewidth=2) ax.fill_between(x, np.minimum(kde1_x, kde2_x), 0, color='lime', alpha=0.3, label='intersection') ax.plot(x[idx], kde2_x[idx], 'ko') handles, labels = ax.get_legend_handles_labels() labels[2] += f': {area * 100:.1f}%' ax.legend(handles, labels) ax.set_title(f'Plot for {col}') # 配置参数 rows = 10 cols = 3 fig = plt.figure(figsize=(30, 4 * rows)) fig.subplots_adjust(hspace=0.3, wspace=0.2) loop_list = ['col1','col2','col3'] actuals = [np.random.randn(100) + i for i in range(3)] predicted = [np.random.randn(100) + i*2 for i in range(3)] # 多进程计算绘图数据 with Pool(processes=None) as pool: my_args = [(actuals[k], predicted[k]) for k in range(len(loop_list))] plot_results = pool.starmap(calculate_plot_data, my_args) # 主进程统一绘图 for k, col in enumerate(loop_list): ax = fig.add_subplot(rows, cols, k+1) plot_data_from_result(ax, plot_results[k], col) # 保存和显示 plt.suptitle('my calculation status', y=0.94, fontsize=18) plt.savefig('./outputs/test.jpg', dpi=150) plt.show()
关键注意点
- 永远不要在多进程间共享matplotlib的
Figure或Axes对象,内存隔离会导致修改无法同步到主进程。 - 多进程只适合做无状态的计算任务,返回计算结果后由主进程处理绘图逻辑。
- 若计算耗时不高,单进程绘图是最省心的选择,避免多进程带来的同步问题。
内容的提问来源于stack exchange,提问作者Obiii
相关产品推荐
相关产品推荐

