|
35 | 35 | import itertools |
36 | 36 | import logging |
37 | 37 | from numbers import Integral |
| 38 | +import threading |
38 | 39 |
|
39 | 40 | import numpy as np |
40 | 41 |
|
@@ -2365,6 +2366,18 @@ class Figure(FigureBase): |
2365 | 2366 | *suppressComposite* is a boolean, this will override the renderer. |
2366 | 2367 | """ |
2367 | 2368 |
|
| 2369 | + # we want to cache the fonts and mathtext at a global level so that when |
| 2370 | + # multiple figures are created we can reuse them. This helps with a bug on |
| 2371 | + # windows where the creation of too many figures leads to too many open |
| 2372 | + # file handles and improves the performance of parsing mathtext. However, |
| 2373 | + # these global caches are not thread safe. The solution here is to let the |
| 2374 | + # Figure acquire a shared lock at the start of the draw, and release it when it |
| 2375 | + # is done. This allows multiple renderers to share the cached fonts and |
| 2376 | + # parsed text, but only one figure can draw at a time and so the font cache |
| 2377 | + # and mathtext cache are used by only one renderer at a time. |
| 2378 | + |
| 2379 | + _render_lock = threading.RLock() |
| 2380 | + |
2368 | 2381 | def __str__(self): |
2369 | 2382 | return "Figure(%gx%g)" % tuple(self.bbox.size) |
2370 | 2383 |
|
@@ -3124,33 +3137,33 @@ def clear(self, keep_observers=False): |
3124 | 3137 | @allow_rasterization |
3125 | 3138 | def draw(self, renderer): |
3126 | 3139 | # docstring inherited |
3127 | | - |
3128 | | - # draw the figure bounding box, perhaps none for white figure |
3129 | 3140 | if not self.get_visible(): |
3130 | 3141 | return |
3131 | 3142 |
|
3132 | | - artists = self._get_draw_artists(renderer) |
3133 | | - try: |
3134 | | - renderer.open_group('figure', gid=self.get_gid()) |
3135 | | - if self.axes and self.get_layout_engine() is not None: |
3136 | | - try: |
3137 | | - self.get_layout_engine().execute(self) |
3138 | | - except ValueError: |
3139 | | - pass |
3140 | | - # ValueError can occur when resizing a window. |
| 3143 | + with self._render_lock: |
3141 | 3144 |
|
3142 | | - self.patch.draw(renderer) |
3143 | | - mimage._draw_list_compositing_images( |
3144 | | - renderer, self, artists, self.suppressComposite) |
| 3145 | + artists = self._get_draw_artists(renderer) |
| 3146 | + try: |
| 3147 | + renderer.open_group('figure', gid=self.get_gid()) |
| 3148 | + if self.axes and self.get_layout_engine() is not None: |
| 3149 | + try: |
| 3150 | + self.get_layout_engine().execute(self) |
| 3151 | + except ValueError: |
| 3152 | + pass |
| 3153 | + # ValueError can occur when resizing a window. |
3145 | 3154 |
|
3146 | | - for sfig in self.subfigs: |
3147 | | - sfig.draw(renderer) |
| 3155 | + self.patch.draw(renderer) |
| 3156 | + mimage._draw_list_compositing_images( |
| 3157 | + renderer, self, artists, self.suppressComposite) |
3148 | 3158 |
|
3149 | | - renderer.close_group('figure') |
3150 | | - finally: |
3151 | | - self.stale = False |
| 3159 | + for sfig in self.subfigs: |
| 3160 | + sfig.draw(renderer) |
| 3161 | + |
| 3162 | + renderer.close_group('figure') |
| 3163 | + finally: |
| 3164 | + self.stale = False |
3152 | 3165 |
|
3153 | | - DrawEvent("draw_event", self.canvas, renderer)._process() |
| 3166 | + DrawEvent("draw_event", self.canvas, renderer)._process() |
3154 | 3167 |
|
3155 | 3168 | def draw_without_rendering(self): |
3156 | 3169 | """ |
|
0 commit comments