CINXE.COM
PEP 556 – Threaded garbage collection | peps.python.org
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="color-scheme" content="light dark"> <title>PEP 556 – Threaded garbage collection | peps.python.org</title> <link rel="shortcut icon" href="../_static/py.png"> <link rel="canonical" href="https://peps.python.org/pep-0556/"> <link rel="stylesheet" href="../_static/style.css" type="text/css"> <link rel="stylesheet" href="../_static/mq.css" type="text/css"> <link rel="stylesheet" href="../_static/pygments.css" type="text/css" media="(prefers-color-scheme: light)" id="pyg-light"> <link rel="stylesheet" href="../_static/pygments_dark.css" type="text/css" media="(prefers-color-scheme: dark)" id="pyg-dark"> <link rel="alternate" type="application/rss+xml" title="Latest PEPs" href="https://peps.python.org/peps.rss"> <meta property="og:title" content='PEP 556 – Threaded garbage collection | peps.python.org'> <meta property="og:description" content="This PEP proposes a new optional mode of operation for CPython’s cyclic garbage collector (GC) where implicit (i.e. opportunistic) collections happen in a dedicated thread rather than synchronously."> <meta property="og:type" content="website"> <meta property="og:url" content="https://peps.python.org/pep-0556/"> <meta property="og:site_name" content="Python Enhancement Proposals (PEPs)"> <meta property="og:image" content="https://peps.python.org/_static/og-image.png"> <meta property="og:image:alt" content="Python PEPs"> <meta property="og:image:width" content="200"> <meta property="og:image:height" content="200"> <meta name="description" content="This PEP proposes a new optional mode of operation for CPython’s cyclic garbage collector (GC) where implicit (i.e. opportunistic) collections happen in a dedicated thread rather than synchronously."> <meta name="theme-color" content="#3776ab"> </head> <body> <svg xmlns="http://www.w3.org/2000/svg" style="display: none;"> <symbol id="svg-sun-half" viewBox="0 0 24 24" pointer-events="all"> <title>Following system colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="9"></circle> <path d="M12 3v18m0-12l4.65-4.65M12 14.3l7.37-7.37M12 19.6l8.85-8.85"></path> </svg> </symbol> <symbol id="svg-moon" viewBox="0 0 24 24" pointer-events="all"> <title>Selected dark colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <path stroke="none" d="M0 0h24v24H0z" fill="none"></path> <path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z"></path> </svg> </symbol> <symbol id="svg-sun" viewBox="0 0 24 24" pointer-events="all"> <title>Selected light colour scheme</title> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"> <circle cx="12" cy="12" r="5"></circle> <line x1="12" y1="1" x2="12" y2="3"></line> <line x1="12" y1="21" x2="12" y2="23"></line> <line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line> <line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line> <line x1="1" y1="12" x2="3" y2="12"></line> <line x1="21" y1="12" x2="23" y2="12"></line> <line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line> <line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line> </svg> </symbol> </svg> <script> document.documentElement.dataset.colour_scheme = localStorage.getItem("colour_scheme") || "auto" </script> <section id="pep-page-section"> <header> <h1>Python Enhancement Proposals</h1> <ul class="breadcrumbs"> <li><a href="https://www.python.org/" title="The Python Programming Language">Python</a> » </li> <li><a href="../pep-0000/">PEP Index</a> » </li> <li>PEP 556</li> </ul> <button id="colour-scheme-cycler" onClick="setColourScheme(nextColourScheme())"> <svg aria-hidden="true" class="colour-scheme-icon-when-auto"><use href="#svg-sun-half"></use></svg> <svg aria-hidden="true" class="colour-scheme-icon-when-dark"><use href="#svg-moon"></use></svg> <svg aria-hidden="true" class="colour-scheme-icon-when-light"><use href="#svg-sun"></use></svg> <span class="visually-hidden">Toggle light / dark / auto colour theme</span> </button> </header> <article> <section id="pep-content"> <h1 class="page-title">PEP 556 – Threaded garbage collection</h1> <dl class="rfc2822 field-list simple"> <dt class="field-odd">Author<span class="colon">:</span></dt> <dd class="field-odd">Antoine Pitrou <solipsis at pitrou.net></dd> <dt class="field-even">Status<span class="colon">:</span></dt> <dd class="field-even"><abbr title="Inactive draft that may be taken up again at a later time">Deferred</abbr></dd> <dt class="field-odd">Type<span class="colon">:</span></dt> <dd class="field-odd"><abbr title="Normative PEP with a new feature for Python, implementation change for CPython or interoperability standard for the ecosystem">Standards Track</abbr></dd> <dt class="field-even">Created<span class="colon">:</span></dt> <dd class="field-even">08-Sep-2017</dd> <dt class="field-odd">Python-Version<span class="colon">:</span></dt> <dd class="field-odd">3.7</dd> <dt class="field-even">Post-History<span class="colon">:</span></dt> <dd class="field-even">08-Sep-2017</dd> </dl> <hr class="docutils" /> <section id="contents"> <details><summary>Table of Contents</summary><ul class="simple"> <li><a class="reference internal" href="#deferral-notice">Deferral Notice</a></li> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#terminology">Terminology</a></li> <li><a class="reference internal" href="#rationale">Rationale</a></li> <li><a class="reference internal" href="#proposal">Proposal</a><ul> <li><a class="reference internal" href="#new-public-apis">New public APIs</a></li> <li><a class="reference internal" href="#intended-use">Intended use</a></li> <li><a class="reference internal" href="#non-goals">Non-goals</a></li> </ul> </li> <li><a class="reference internal" href="#internal-details">Internal details</a><ul> <li><a class="reference internal" href="#gc-module"><code class="docutils literal notranslate"><span class="pre">gc</span></code> module</a></li> <li><a class="reference internal" href="#threading-module"><code class="docutils literal notranslate"><span class="pre">threading</span></code> module</a></li> <li><a class="reference internal" href="#pseudo-code">Pseudo-code</a></li> </ul> </li> <li><a class="reference internal" href="#discussion">Discussion</a><ul> <li><a class="reference internal" href="#default-mode">Default mode</a></li> <li><a class="reference internal" href="#explicit-collections">Explicit collections</a></li> <li><a class="reference internal" href="#impact-on-memory-use">Impact on memory use</a></li> <li><a class="reference internal" href="#impact-on-cpu-consumption">Impact on CPU consumption</a></li> <li><a class="reference internal" href="#impact-on-gc-pauses">Impact on GC pauses</a></li> </ul> </li> <li><a class="reference internal" href="#open-issues">Open issues</a></li> <li><a class="reference internal" href="#implementation">Implementation</a></li> <li><a class="reference internal" href="#references">References</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> </details></section> <section id="deferral-notice"> <h2><a class="toc-backref" href="#deferral-notice" role="doc-backlink">Deferral Notice</a></h2> <p>This PEP is currently not being actively worked on. It may be revived in the future. The main missing steps are:</p> <ul class="simple"> <li>polish the implementation, adapting the test suite where necessary;</li> <li>ensure setting threaded garbage collection does not disrupt existing code in unexpected ways (expected impact includes lengthening the lifetime of objects in reference cycles).</li> </ul> </section> <section id="abstract"> <h2><a class="toc-backref" href="#abstract" role="doc-backlink">Abstract</a></h2> <p>This PEP proposes a new optional mode of operation for CPython’s cyclic garbage collector (GC) where implicit (i.e. opportunistic) collections happen in a dedicated thread rather than synchronously.</p> </section> <section id="terminology"> <h2><a class="toc-backref" href="#terminology" role="doc-backlink">Terminology</a></h2> <p>An “implicit” GC run (or “implicit” collection) is one that is triggered opportunistically based on a certain heuristic computed over allocation statistics, whenever a new allocation is requested. Details of the heuristic are not relevant to this PEP, as it does not propose to change it.</p> <p>An “explicit” GC run (or “explicit” collection) is one that is requested programmatically by an API call such as <code class="docutils literal notranslate"><span class="pre">gc.collect</span></code>.</p> <p>“Threaded” refers to the fact that GC runs happen in a dedicated thread separate from sequential execution of application code. It does not mean “concurrent” (the Global Interpreter Lock, or GIL, still serializes execution among Python threads <em>including</em> the dedicated GC thread) nor “parallel” (the GC is not able to distribute its work onto several threads at once to lower wall-clock latencies of GC runs).</p> </section> <section id="rationale"> <h2><a class="toc-backref" href="#rationale" role="doc-backlink">Rationale</a></h2> <p>The mode of operation for the GC has always been to perform implicit collections synchronously. That is, whenever the aforementioned heuristic is activated, execution of application code in the current thread is suspended and the GC is launched in order to reclaim dead reference cycles.</p> <p>There is a catch, though. Over the course of reclaiming dead reference cycles (and any ancillary objects hanging at those cycles), the GC can execute arbitrary finalization code in the form of <code class="docutils literal notranslate"><span class="pre">__del__</span></code> methods and <code class="docutils literal notranslate"><span class="pre">weakref</span></code> callbacks. Over the years, Python has been used for more and more sophisticated purposes, and it is increasingly common for finalization code to perform complex tasks, for example in distributed systems where loss of an object may require notifying other (logical or physical) nodes.</p> <p>Interrupting application code at arbitrary points to execute finalization code that may rely on a consistent internal state and/or on acquiring synchronization primitives gives rise to reentrancy issues that even the most seasoned experts have trouble fixing properly <a class="footnote-reference brackets" href="#queue-reentrancy-bug" id="id1">[1]</a>.</p> <p>This PEP bases itself on the observation that, despite the apparent similarities, same-thread reentrancy is a fundamentally harder problem than multi-thread synchronization. Instead of letting each developer or library author struggle with extremely hard reentrancy issues, one by one, this PEP proposes to allow the GC to run in a separate thread where well-known multi-thread synchronization practices are sufficient.</p> </section> <section id="proposal"> <h2><a class="toc-backref" href="#proposal" role="doc-backlink">Proposal</a></h2> <p>Under this PEP, the GC has two modes of operation:</p> <ul class="simple"> <li>“serial”, which is the default and legacy mode, where an implicit GC run is performed immediately in the thread that detects such an implicit run is desired (based on the aforementioned allocation heuristic).</li> <li>“threaded”, which can be explicitly enabled at runtime on a per-process basis, where implicit GC runs are <em>scheduled</em> whenever the allocation heuristic is triggered, but run in a dedicated background thread.</li> </ul> <p>Hard reentrancy problems which plague sophisticated uses of finalization callbacks in the “serial” mode become relatively easy multi-thread synchronization problems in the “threaded” mode of operation.</p> <p>The GC also traditionally allows for explicit GC runs, using the Python API <code class="docutils literal notranslate"><span class="pre">gc.collect</span></code> and the C API <code class="docutils literal notranslate"><span class="pre">PyGC_Collect</span></code>. The visible semantics of these two APIs are left unchanged: they perform a GC run immediately when called, and only return when the GC run is finished.</p> <section id="new-public-apis"> <h3><a class="toc-backref" href="#new-public-apis" role="doc-backlink">New public APIs</a></h3> <p>Two new Python APIs are added to the <code class="docutils literal notranslate"><span class="pre">gc</span></code> module:</p> <ul class="simple"> <li><code class="docutils literal notranslate"><span class="pre">gc.set_mode(mode)</span></code> sets the current mode of operation (either “serial” or “threaded”). If setting to “serial” and the current mode is “threaded”, then the function also waits for the GC thread to end.</li> <li><code class="docutils literal notranslate"><span class="pre">gc.get_mode()</span></code> returns the current mode of operation.</li> </ul> <p>It is allowed to switch back and forth between modes of operation.</p> </section> <section id="intended-use"> <h3><a class="toc-backref" href="#intended-use" role="doc-backlink">Intended use</a></h3> <p>Given the per-process nature of the switch and its repercussions on semantics of all finalization callbacks, it is recommended that it is set at the beginning of an application’s code (and/or in initializers for child processes e.g. when using <code class="docutils literal notranslate"><span class="pre">multiprocessing</span></code>). Library functions should probably not mess with this setting, just as they shouldn’t call <code class="docutils literal notranslate"><span class="pre">gc.enable</span></code> or <code class="docutils literal notranslate"><span class="pre">gc.disable</span></code>, but there’s nothing to prevent them from doing so.</p> </section> <section id="non-goals"> <h3><a class="toc-backref" href="#non-goals" role="doc-backlink">Non-goals</a></h3> <p>This PEP does not address reentrancy issues with other kinds of asynchronous code execution (for example signal handlers registered with the <code class="docutils literal notranslate"><span class="pre">signal</span></code> module). The author believes that the overwhelming majority of painful reentrancy issues occur with finalizers. Most of the time, signal handlers are able to set a single flag and/or wake up a file descriptor for the main program to notice. As for those signal handlers which raise an exception, they <em>have</em> to execute in-thread.</p> <p>This PEP also does not change the execution of finalization callbacks when they are called as part of regular reference counting, i.e. when releasing a visible reference drops an object’s reference count to zero. Since such execution happens at deterministic points in code, it is usually not a problem.</p> </section> </section> <section id="internal-details"> <h2><a class="toc-backref" href="#internal-details" role="doc-backlink">Internal details</a></h2> <p>TODO: Update this section to conform to the current implementation.</p> <section id="gc-module"> <h3><a class="toc-backref" href="#gc-module" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">gc</span></code> module</a></h3> <p>An internal flag <code class="docutils literal notranslate"><span class="pre">gc_is_threaded</span></code> is added, telling whether GC is serial or threaded.</p> <p>An internal structure <code class="docutils literal notranslate"><span class="pre">gc_mutex</span></code> is added to avoid two GC runs at once:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">static</span> <span class="n">struct</span> <span class="p">{</span> <span class="n">PyThread_type_lock</span> <span class="n">lock</span><span class="p">;</span> <span class="o">/*</span> <span class="n">taken</span> <span class="n">when</span> <span class="n">collecting</span> <span class="o">*/</span> <span class="n">PyThreadState</span> <span class="o">*</span><span class="n">owner</span><span class="p">;</span> <span class="o">/*</span> <span class="n">whichever</span> <span class="n">thread</span> <span class="ow">is</span> <span class="n">currently</span> <span class="n">collecting</span> <span class="p">(</span><span class="n">NULL</span> <span class="k">if</span> <span class="n">no</span> <span class="n">collection</span> <span class="ow">is</span> <span class="n">taking</span> <span class="n">place</span><span class="p">)</span> <span class="o">*/</span> <span class="p">}</span> <span class="n">gc_mutex</span><span class="p">;</span> </pre></div> </div> <p>An internal structure <code class="docutils literal notranslate"><span class="pre">gc_thread</span></code> is added to handle synchronization with the GC thread:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="n">static</span> <span class="n">struct</span> <span class="p">{</span> <span class="n">PyThread_type_lock</span> <span class="n">wakeup</span><span class="p">;</span> <span class="o">/*</span> <span class="n">acts</span> <span class="k">as</span> <span class="n">an</span> <span class="n">event</span> <span class="n">to</span> <span class="n">wake</span> <span class="n">up</span> <span class="n">the</span> <span class="n">GC</span> <span class="n">thread</span> <span class="o">*/</span> <span class="nb">int</span> <span class="n">collection_requested</span><span class="p">;</span> <span class="o">/*</span> <span class="n">non</span><span class="o">-</span><span class="n">zero</span> <span class="k">if</span> <span class="n">collection</span> <span class="n">requested</span> <span class="o">*/</span> <span class="n">PyThread_type_lock</span> <span class="n">done</span><span class="p">;</span> <span class="o">/*</span> <span class="n">acts</span> <span class="k">as</span> <span class="n">an</span> <span class="n">event</span> <span class="n">signaling</span> <span class="n">the</span> <span class="n">GC</span> <span class="n">thread</span> <span class="n">has</span> <span class="n">exited</span> <span class="o">*/</span> <span class="p">}</span> <span class="n">gc_thread</span><span class="p">;</span> </pre></div> </div> </section> <section id="threading-module"> <h3><a class="toc-backref" href="#threading-module" role="doc-backlink"><code class="docutils literal notranslate"><span class="pre">threading</span></code> module</a></h3> <p>Two private functions are added to the <code class="docutils literal notranslate"><span class="pre">threading</span></code> module:</p> <ul class="simple"> <li><code class="docutils literal notranslate"><span class="pre">threading._ensure_dummy_thread(name)</span></code> creates and registers a <code class="docutils literal notranslate"><span class="pre">Thread</span></code> instance for the current thread with the given <em>name</em>, and returns it.</li> <li><code class="docutils literal notranslate"><span class="pre">threading._remove_dummy_thread(thread)</span></code> removes the given <em>thread</em> (as returned by <code class="docutils literal notranslate"><span class="pre">_ensure_dummy_thread</span></code>) from the threading module’s internal state.</li> </ul> <p>The purpose of these two functions is to improve debugging and introspection by letting <code class="docutils literal notranslate"><span class="pre">threading.current_thread()</span></code> return a more meaningfully-named object when called inside a finalization callback in the GC thread.</p> </section> <section id="pseudo-code"> <h3><a class="toc-backref" href="#pseudo-code" role="doc-backlink">Pseudo-code</a></h3> <p>Here is a proposed pseudo-code for the main primitives, public and internal, required for implementing this PEP. All of them will be implemented in C and live inside the <code class="docutils literal notranslate"><span class="pre">gc</span></code> module, unless otherwise noted:</p> <div class="highlight-default notranslate"><div class="highlight"><pre><span></span><span class="k">def</span><span class="w"> </span><span class="nf">collect_with_callback</span><span class="p">(</span><span class="n">generation</span><span class="p">):</span> <span class="w"> </span><span class="sd">"""</span> <span class="sd"> Collect up to the given *generation*.</span> <span class="sd"> """</span> <span class="c1"># Same code as currently (see collect_with_callback() in gcmodule.c)</span> <span class="k">def</span><span class="w"> </span><span class="nf">collect_generations</span><span class="p">():</span> <span class="w"> </span><span class="sd">"""</span> <span class="sd"> Collect as many generations as desired by the heuristic.</span> <span class="sd"> """</span> <span class="c1"># Same code as currently (see collect_generations() in gcmodule.c)</span> <span class="k">def</span><span class="w"> </span><span class="nf">lock_and_collect</span><span class="p">(</span><span class="n">generation</span><span class="o">=-</span><span class="mi">1</span><span class="p">):</span> <span class="w"> </span><span class="sd">"""</span> <span class="sd"> Perform a collection with thread safety.</span> <span class="sd"> """</span> <span class="n">me</span> <span class="o">=</span> <span class="n">PyThreadState_GET</span><span class="p">()</span> <span class="k">if</span> <span class="n">gc_mutex</span><span class="o">.</span><span class="n">owner</span> <span class="o">==</span> <span class="n">me</span><span class="p">:</span> <span class="c1"># reentrant GC collection request, bail out</span> <span class="k">return</span> <span class="n">Py_BEGIN_ALLOW_THREADS</span> <span class="n">gc_mutex</span><span class="o">.</span><span class="n">lock</span><span class="o">.</span><span class="n">acquire</span><span class="p">()</span> <span class="n">Py_END_ALLOW_THREADS</span> <span class="n">gc_mutex</span><span class="o">.</span><span class="n">owner</span> <span class="o">=</span> <span class="n">me</span> <span class="k">try</span><span class="p">:</span> <span class="k">if</span> <span class="n">generation</span> <span class="o">>=</span> <span class="mi">0</span><span class="p">:</span> <span class="k">return</span> <span class="n">collect_with_callback</span><span class="p">(</span><span class="n">generation</span><span class="p">)</span> <span class="k">else</span><span class="p">:</span> <span class="k">return</span> <span class="n">collect_generations</span><span class="p">()</span> <span class="k">finally</span><span class="p">:</span> <span class="n">gc_mutex</span><span class="o">.</span><span class="n">owner</span> <span class="o">=</span> <span class="n">NULL</span> <span class="n">gc_mutex</span><span class="o">.</span><span class="n">lock</span><span class="o">.</span><span class="n">release</span><span class="p">()</span> <span class="k">def</span><span class="w"> </span><span class="nf">schedule_gc_request</span><span class="p">():</span> <span class="w"> </span><span class="sd">"""</span> <span class="sd"> Ask the GC thread to run an implicit collection.</span> <span class="sd"> """</span> <span class="k">assert</span> <span class="n">gc_is_threaded</span> <span class="o">==</span> <span class="kc">True</span> <span class="c1"># Note this is extremely fast if a collection is already requested</span> <span class="k">if</span> <span class="n">gc_thread</span><span class="o">.</span><span class="n">collection_requested</span> <span class="o">==</span> <span class="kc">False</span><span class="p">:</span> <span class="n">gc_thread</span><span class="o">.</span><span class="n">collection_requested</span> <span class="o">=</span> <span class="kc">True</span> <span class="n">gc_thread</span><span class="o">.</span><span class="n">wakeup</span><span class="o">.</span><span class="n">release</span><span class="p">()</span> <span class="k">def</span><span class="w"> </span><span class="nf">is_implicit_gc_desired</span><span class="p">():</span> <span class="w"> </span><span class="sd">"""</span> <span class="sd"> Whether an implicit GC run is currently desired based on allocation</span> <span class="sd"> stats. Return a generation number, or -1 if none desired.</span> <span class="sd"> """</span> <span class="c1"># Same heuristic as currently (see _PyObject_GC_Alloc in gcmodule.c)</span> <span class="k">def</span><span class="w"> </span><span class="nf">PyGC_Malloc</span><span class="p">():</span> <span class="w"> </span><span class="sd">"""</span> <span class="sd"> Allocate a GC-enabled object.</span> <span class="sd"> """</span> <span class="c1"># Update allocation statistics (same code as currently, omitted for brevity)</span> <span class="k">if</span> <span class="n">is_implicit_gc_desired</span><span class="p">():</span> <span class="k">if</span> <span class="n">gc_is_threaded</span><span class="p">:</span> <span class="n">schedule_gc_request</span><span class="p">()</span> <span class="k">else</span><span class="p">:</span> <span class="n">lock_and_collect</span><span class="p">()</span> <span class="c1"># Go ahead with allocation (same code as currently, omitted for brevity)</span> <span class="k">def</span><span class="w"> </span><span class="nf">gc_thread</span><span class="p">(</span><span class="n">interp_state</span><span class="p">):</span> <span class="w"> </span><span class="sd">"""</span> <span class="sd"> Dedicated loop for threaded GC.</span> <span class="sd"> """</span> <span class="c1"># Init Python thread state (omitted, see t_bootstrap in _threadmodule.c)</span> <span class="c1"># Optional: init thread in Python threading module, for better introspection</span> <span class="n">me</span> <span class="o">=</span> <span class="n">threading</span><span class="o">.</span><span class="n">_ensure_dummy_thread</span><span class="p">(</span><span class="n">name</span><span class="o">=</span><span class="s2">"GC thread"</span><span class="p">)</span> <span class="k">while</span> <span class="n">gc_is_threaded</span> <span class="o">==</span> <span class="kc">True</span><span class="p">:</span> <span class="n">Py_BEGIN_ALLOW_THREADS</span> <span class="n">gc_thread</span><span class="o">.</span><span class="n">wakeup</span><span class="o">.</span><span class="n">acquire</span><span class="p">()</span> <span class="n">Py_END_ALLOW_THREADS</span> <span class="k">if</span> <span class="n">gc_thread</span><span class="o">.</span><span class="n">collection_requested</span> <span class="o">!=</span> <span class="mi">0</span><span class="p">:</span> <span class="n">gc_thread</span><span class="o">.</span><span class="n">collection_requested</span> <span class="o">=</span> <span class="mi">0</span> <span class="n">lock_and_collect</span><span class="p">(</span><span class="n">generation</span><span class="o">=-</span><span class="mi">1</span><span class="p">)</span> <span class="n">threading</span><span class="o">.</span><span class="n">_remove_dummy_thread</span><span class="p">(</span><span class="n">me</span><span class="p">)</span> <span class="c1"># Signal we're exiting</span> <span class="n">gc_thread</span><span class="o">.</span><span class="n">done</span><span class="o">.</span><span class="n">release</span><span class="p">()</span> <span class="c1"># Free Python thread state (omitted)</span> <span class="k">def</span><span class="w"> </span><span class="nf">gc</span><span class="o">.</span><span class="n">set_mode</span><span class="p">(</span><span class="n">mode</span><span class="p">):</span> <span class="w"> </span><span class="sd">"""</span> <span class="sd"> Set current GC mode. This is a process-global setting.</span> <span class="sd"> """</span> <span class="k">if</span> <span class="n">mode</span> <span class="o">==</span> <span class="s2">"threaded"</span><span class="p">:</span> <span class="k">if</span> <span class="ow">not</span> <span class="n">gc_is_threaded</span> <span class="o">==</span> <span class="kc">False</span><span class="p">:</span> <span class="c1"># Launch thread</span> <span class="n">gc_thread</span><span class="o">.</span><span class="n">done</span><span class="o">.</span><span class="n">acquire</span><span class="p">(</span><span class="n">block</span><span class="o">=</span><span class="kc">False</span><span class="p">)</span> <span class="c1"># should not fail</span> <span class="n">gc_is_threaded</span> <span class="o">=</span> <span class="kc">True</span> <span class="n">PyThread_start_new_thread</span><span class="p">(</span><span class="n">gc_thread</span><span class="p">)</span> <span class="k">elif</span> <span class="n">mode</span> <span class="o">==</span> <span class="s2">"serial"</span><span class="p">:</span> <span class="k">if</span> <span class="n">gc_is_threaded</span> <span class="o">==</span> <span class="kc">True</span><span class="p">:</span> <span class="c1"># Wake up thread, asking it to end</span> <span class="n">gc_is_threaded</span> <span class="o">=</span> <span class="kc">False</span> <span class="n">gc_thread</span><span class="o">.</span><span class="n">wakeup</span><span class="o">.</span><span class="n">release</span><span class="p">()</span> <span class="c1"># Wait for thread exit</span> <span class="n">Py_BEGIN_ALLOW_THREADS</span> <span class="n">gc_thread</span><span class="o">.</span><span class="n">done</span><span class="o">.</span><span class="n">acquire</span><span class="p">()</span> <span class="n">Py_END_ALLOW_THREADS</span> <span class="n">gc_thread</span><span class="o">.</span><span class="n">done</span><span class="o">.</span><span class="n">release</span><span class="p">()</span> <span class="k">else</span><span class="p">:</span> <span class="k">raise</span> <span class="ne">ValueError</span><span class="p">(</span><span class="s2">"unsupported mode </span><span class="si">%r</span><span class="s2">"</span> <span class="o">%</span> <span class="p">(</span><span class="n">mode</span><span class="p">,))</span> <span class="k">def</span><span class="w"> </span><span class="nf">gc</span><span class="o">.</span><span class="n">get_mode</span><span class="p">(</span><span class="n">mode</span><span class="p">):</span> <span class="w"> </span><span class="sd">"""</span> <span class="sd"> Get current GC mode.</span> <span class="sd"> """</span> <span class="k">return</span> <span class="s2">"threaded"</span> <span class="k">if</span> <span class="n">gc_is_threaded</span> <span class="k">else</span> <span class="s2">"serial"</span> <span class="k">def</span><span class="w"> </span><span class="nf">gc</span><span class="o">.</span><span class="n">collect</span><span class="p">(</span><span class="n">generation</span><span class="o">=</span><span class="mi">2</span><span class="p">):</span> <span class="w"> </span><span class="sd">"""</span> <span class="sd"> Schedule collection of the given generation and wait for it to</span> <span class="sd"> finish.</span> <span class="sd"> """</span> <span class="k">return</span> <span class="n">lock_and_collect</span><span class="p">(</span><span class="n">generation</span><span class="p">)</span> </pre></div> </div> </section> </section> <section id="discussion"> <h2><a class="toc-backref" href="#discussion" role="doc-backlink">Discussion</a></h2> <section id="default-mode"> <h3><a class="toc-backref" href="#default-mode" role="doc-backlink">Default mode</a></h3> <p>One may wonder whether the default mode should simply be changed to “threaded”. For multi-threaded applications, it would probably not be a problem: those applications must already be prepared for finalization handlers to be run in arbitrary threads. In single-thread applications, however, it is currently guaranteed that finalizers will always be called in the main thread. Breaking this property may induce subtle behaviour changes or bugs, for example if finalizers rely on some thread-local values.</p> <p>Another problem is when a program uses <code class="docutils literal notranslate"><span class="pre">fork()</span></code> for concurrency. Calling <code class="docutils literal notranslate"><span class="pre">fork()</span></code> from a single-threaded program is safe, but it’s fragile (to say the least) if the program is multi-threaded.</p> </section> <section id="explicit-collections"> <h3><a class="toc-backref" href="#explicit-collections" role="doc-backlink">Explicit collections</a></h3> <p>One may ask whether explicit collections should also be delegated to the background thread. The answer is it doesn’t really matter: since <code class="docutils literal notranslate"><span class="pre">gc.collect</span></code> and <code class="docutils literal notranslate"><span class="pre">PyGC_Collect</span></code> actually <em>wait</em> for the collection to end (breaking this property would break compatibility), delegating the actual work to a background thread wouldn’t ease synchronization with the thread requesting an explicit collection.</p> <p>In the end, this PEP choses the behaviour that seems simpler to implement based on the pseudo-code above.</p> </section> <section id="impact-on-memory-use"> <h3><a class="toc-backref" href="#impact-on-memory-use" role="doc-backlink">Impact on memory use</a></h3> <p>The “threaded” mode incurs a slight delay in implicit collections compared to the default “serial” mode. This obviously may change the memory profile of certain applications. By how much remains to be measured in real-world use, but we expect the impact to remain minor and bearable. First because implicit collections are based on a <em>heuristic</em> whose effect does not result in deterministic visible behaviour anyway. Second because the GC deals with reference cycles while many objects are reclaimed immediately when their last visible reference disappears.</p> </section> <section id="impact-on-cpu-consumption"> <h3><a class="toc-backref" href="#impact-on-cpu-consumption" role="doc-backlink">Impact on CPU consumption</a></h3> <p>The pseudo-code above adds two lock operations for each implicit collection request in “threaded” mode: one in the thread making the request (a <code class="docutils literal notranslate"><span class="pre">release</span></code> call) and one in the GC thread (an <code class="docutils literal notranslate"><span class="pre">acquire</span></code> call). It also adds two other lock operations, regardless of the current mode, around each actual collection.</p> <p>We expect the cost of those lock operations to be very small, on modern systems, compared to the actual cost of crawling through the chains of pointers during the collection itself (“pointer chasing” being one of the hardest workloads on modern CPUs, as it lends itself poorly to speculation and superscalar execution).</p> <p>Actual measurements on worst-case mini-benchmarks may help provide reassuring upper bounds.</p> </section> <section id="impact-on-gc-pauses"> <h3><a class="toc-backref" href="#impact-on-gc-pauses" role="doc-backlink">Impact on GC pauses</a></h3> <p>While this PEP does not concern itself with GC pauses, there is a practical chance that releasing the GIL at some point during an implicit collection (for example by virtue of executing a pure Python finalizer) will allow application code to run in-between, lowering the <em>visible</em> GC pause time for some applications.</p> <p>If this PEP is accepted, future work may try to better realize this potential by speculatively releasing the GIL during collections, though it is unclear how doable that is.</p> </section> </section> <section id="open-issues"> <h2><a class="toc-backref" href="#open-issues" role="doc-backlink">Open issues</a></h2> <ul class="simple"> <li><code class="docutils literal notranslate"><span class="pre">gc.set_mode</span></code> should probably be protected against multiple concurrent invocations. Also, it should raise when called from <em>inside</em> a GC run (i.e. from a finalizer).</li> <li>What happens at shutdown? Does the GC thread run until <code class="docutils literal notranslate"><span class="pre">_PyGC_Fini()</span></code> is called?</li> </ul> </section> <section id="implementation"> <h2><a class="toc-backref" href="#implementation" role="doc-backlink">Implementation</a></h2> <p>A draft implementation is available in the <code class="docutils literal notranslate"><span class="pre">threaded_gc</span></code> branch <a class="footnote-reference brackets" href="#implementation-branch" id="id2">[2]</a> of the author’s Github fork <a class="footnote-reference brackets" href="#cpython-pitrou-fork" id="id3">[3]</a>.</p> </section> <section id="references"> <h2><a class="toc-backref" href="#references" role="doc-backlink">References</a></h2> <aside class="footnote-list brackets"> <aside class="footnote brackets" id="queue-reentrancy-bug" role="doc-footnote"> <dt class="label" id="queue-reentrancy-bug">[<a href="#id1">1</a>]</dt> <dd><a class="reference external" href="https://bugs.python.org/issue14976">https://bugs.python.org/issue14976</a></aside> <aside class="footnote brackets" id="implementation-branch" role="doc-footnote"> <dt class="label" id="implementation-branch">[<a href="#id2">2</a>]</dt> <dd><a class="reference external" href="https://github.com/pitrou/cpython/tree/threaded_gc">https://github.com/pitrou/cpython/tree/threaded_gc</a></aside> <aside class="footnote brackets" id="cpython-pitrou-fork" role="doc-footnote"> <dt class="label" id="cpython-pitrou-fork">[<a href="#id3">3</a>]</dt> <dd><a class="reference external" href="https://github.com/pitrou/cpython/">https://github.com/pitrou/cpython/</a></aside> </aside> </section> <section id="copyright"> <h2><a class="toc-backref" href="#copyright" role="doc-backlink">Copyright</a></h2> <p>This document has been placed in the public domain.</p> </section> </section> <hr class="docutils" /> <p>Source: <a class="reference external" href="https://github.com/python/peps/blob/main/peps/pep-0556.rst">https://github.com/python/peps/blob/main/peps/pep-0556.rst</a></p> <p>Last modified: <a class="reference external" href="https://github.com/python/peps/commits/main/peps/pep-0556.rst">2025-02-01 08:55:40 GMT</a></p> </article> <nav id="pep-sidebar"> <h2>Contents</h2> <ul> <li><a class="reference internal" href="#deferral-notice">Deferral Notice</a></li> <li><a class="reference internal" href="#abstract">Abstract</a></li> <li><a class="reference internal" href="#terminology">Terminology</a></li> <li><a class="reference internal" href="#rationale">Rationale</a></li> <li><a class="reference internal" href="#proposal">Proposal</a><ul> <li><a class="reference internal" href="#new-public-apis">New public APIs</a></li> <li><a class="reference internal" href="#intended-use">Intended use</a></li> <li><a class="reference internal" href="#non-goals">Non-goals</a></li> </ul> </li> <li><a class="reference internal" href="#internal-details">Internal details</a><ul> <li><a class="reference internal" href="#gc-module"><code class="docutils literal notranslate"><span class="pre">gc</span></code> module</a></li> <li><a class="reference internal" href="#threading-module"><code class="docutils literal notranslate"><span class="pre">threading</span></code> module</a></li> <li><a class="reference internal" href="#pseudo-code">Pseudo-code</a></li> </ul> </li> <li><a class="reference internal" href="#discussion">Discussion</a><ul> <li><a class="reference internal" href="#default-mode">Default mode</a></li> <li><a class="reference internal" href="#explicit-collections">Explicit collections</a></li> <li><a class="reference internal" href="#impact-on-memory-use">Impact on memory use</a></li> <li><a class="reference internal" href="#impact-on-cpu-consumption">Impact on CPU consumption</a></li> <li><a class="reference internal" href="#impact-on-gc-pauses">Impact on GC pauses</a></li> </ul> </li> <li><a class="reference internal" href="#open-issues">Open issues</a></li> <li><a class="reference internal" href="#implementation">Implementation</a></li> <li><a class="reference internal" href="#references">References</a></li> <li><a class="reference internal" href="#copyright">Copyright</a></li> </ul> <br> <a id="source" href="https://github.com/python/peps/blob/main/peps/pep-0556.rst">Page Source (GitHub)</a> </nav> </section> <script src="../_static/colour_scheme.js"></script> <script src="../_static/wrap_tables.js"></script> <script src="../_static/sticky_banner.js"></script> </body> </html>