Python 使用savefig的Matplotlib多处理字体损坏

Python 使用savefig的Matplotlib多处理字体损坏,python,matplotlib,multiprocessing,Python,Matplotlib,Multiprocessing,自版本1.5以来,我在Matplotlib中遇到了多处理问题。字体在其原始位置周围随机跳跃。例如: 重现此错误的简单示例如下: import multiprocessing import matplotlib.pyplot as plt fig = plt.figure() def plot(i): fig = plt.gcf() plt.plot([],[]) fig.savefig('%d.png' % i) plot(0) pool = multiproce

自版本1.5以来,我在Matplotlib中遇到了多处理问题。字体在其原始位置周围随机跳跃。例如:

重现此错误的简单示例如下:

import multiprocessing
import matplotlib.pyplot as plt

fig = plt.figure()

def plot(i):
    fig = plt.gcf()
    plt.plot([],[])
    fig.savefig('%d.png' % i)

plot(0)
pool = multiprocessing.Pool(4)
pool.map(plot, range(10))
如果多处理和简单打印的顺序颠倒

pool = multiprocessing.Pool(4)
plot(0)
pool.map(plot, range(10))
然后它就工作了,但是这个变通方法对我来说是无用的


多谢各位

我最近在测试并行绘制大量绘图的方法时遇到了同样的问题。虽然我还没有找到使用多处理模块的解决方案,但我发现使用并行Python包()时没有看到相同的错误。在我早期的测试中,它似乎比多处理模块慢约50%,但与串行绘图相比,仍然有显著的加速。对于模块导入也有点挑剔,所以我最终还是希望找到一个使用多处理的解决方案,但现在这是一个可行的解决方案(至少对我来说)。也就是说,我对并行处理非常陌生,所以这两种方法可能有一些细微差别,我在这里没有提到

###############################################################################
import os
import sys
import time
#import numpy as np
import numpy    # Importing with 'as' doesn't work with Parallel Python
#import matplotlib.pyplot as plt
import matplotlib.pyplot    # Importing with 'as' doesn't work with Parallel Python
import pp
import multiprocessing as mp
###############################################################################
path1='./Test_PP'
path2='./Test_MP'
nplots=100
###############################################################################
def plotrandom(plotid,N,path):
    numpy.random.seed() # Required for multiprocessing module but not Parallel Python...
    x=numpy.random.randn(N)
    y=x**2
    matplotlib.pyplot.scatter(x,y)
    matplotlib.pyplot.savefig(os.path.join(path,'test_%d.png'%(plotid)),dpi=150)
    matplotlib.pyplot.close('all')
##############################################################################    #
# Parallel Python implementation
tstart_1=time.time()
if not os.path.exists(path1):
    os.makedirs(path1)

ppservers = ()

if len(sys.argv) > 1:
    ncpus = int(sys.argv[1])
    job_server = pp.Server(ncpus, ppservers=ppservers)
else:
    job_server = pp.Server(ppservers=ppservers)

print "Starting Parallel Python v2 with", job_server.get_ncpus(), "workers"

jobs = [(input_i, job_server.submit(plotrandom,(input_i,10,path1),(),("numpy","matplotlib.pyplot"))) for input_i in range(nplots)]

for input_i, job in jobs:
    job()

tend_1=time.time()
t1=tend_1-tstart_1
print 'Parallel Python = %0.5f sec'%(t1)
job_server.print_stats()
##############################################################################    #
# Multiprocessing implementation
tstart_2=time.time()
if not os.path.exists(path2):
    os.makedirs(path2)

if len(sys.argv) > 1:
    ncpus = int(sys.argv[1])
else:
    ncpus = mp.cpu_count()

print "Starting multiprocessing v2 with %d workers"%(ncpus)

pool = mp.Pool(processes=ncpus)
jobs = [pool.apply_async(plotrandom, args=(i,10,path2)) for i in range(nplots)]
results = [r.get() for r in jobs]    # This line actually runs the jobs
pool.close()
pool.join()

tend_2=time.time()
t2=tend_2-tstart_2
print 'Multiprocessing = %0.5f sec'%(t2)
###############################################################################

我找到了解决办法。问题的主要原因是/matplotlib/backends/backend_agg.py中的dictionary_fontd中的字体缓存

因此,我为每个进程添加了一个不同的散列,将multiprocessing.current_process().pid添加到函数_get_agg_font中名为key的散列中


如果有人知道更优雅的解决方案,不需要修改matplotlib文件,请告诉我

这里显示了我是如何在backend\u agg.py中更改函数的:

from multiprocessing import current_process
def _get_agg_font(self, prop):
    """
    Get the font for text instance t, cacheing for efficiency
    """
    if __debug__: verbose.report('RendererAgg._get_agg_font',
                                 'debug-annoying')

    key = hash(prop)
    key += current_process().pid

    font = RendererAgg._fontd.get(key)

    if font is None:
        fname = findfont(prop)
        #font = RendererAgg._fontd.get(fname)
        if font is None:
            font = FT2Font(
                fname,
                hinting_factor=rcParams['text.hinting_factor'])
            RendererAgg._fontd[fname] = font
        RendererAgg._fontd[key] = font

    font.clear()
    size = prop.get_size_in_points()
    font.set_size(size, self.dpi)

    return font

解决方法是将matplotlib导入放在传递给多处理的函数中。

我也遇到了同样的问题:同时使用缓存字体会在文本打印中产生小故障(例如轴号)。问题似乎是lru缓存的字体缓存:

对于我来说,升级到Python3.7解决了这个问题(它显然支持分叉后的清理状态)

在workers中运行以下命令也可能有所帮助:

import matplotlib
matplotlib.font_manager._get_font.cache_clear()

您假设对绘图逻辑的并发访问是安全的。如果它真的是,我会很惊讶。可能不是,但它使用的是旧的matplotlib,有时也使用新的matplotlib。我只需要一种方法,如何创建和保存的多重处理情节。绘制情节是非常类似于现实生活中的绘画。拥有多个艺术家通常不会改善结果。:)是的,我知道,但它使用的是较旧的matplotlib版本,如果在标准绘图之前进行多处理,它也可以工作。我相信这是找到解决方案的关键。但我不明白为什么。在创建绘图时,是否有一些数量或变量发生了变化?是否可以重新开始matplotlib?我曾尝试在没有pylab的情况下进行绘图,但结果是一样的。到目前为止,我只发现了一个丑陋的解决方法,我必须在主威胁过程之外调用savefig(或实际上在savefig内部绘制)(target=fig.savefig,args=(name,).start(),但这并不总是可能的。请您详细说明一下?我对后端不太熟悉…你能用一个示例代码片段为你的答案添加更多细节吗?我多年来一直试图解决这个问题。把它作为绘图函数的第一行,最终解决了这个问题。欢迎来到stack overflow Oliver——很棒的第一篇帖子!