Expected future for PEP 734?

FTR, I think this is completely awesome! I definitely encourage folks to try it out. I expect that people are going to figure out cool ways to use multiple interpreters that I never dreamed of. :smile:

This is definitely where the most uncertainty is. I tried to come up with a bare minimum mechanism (PEP 734 queues and PEP 554 channels), but there are many possibilities to explore one we can open the floodgates.

I purposefully did not add a cross-interpreter lock type. My expectation has been that the concurrency model that multiple interpreters naturally support will make the most sense if synchronization is more closely tied to the communication-focused data types (queues and channels). In theory they should be sufficient and flow better.

That said, we currently don’t have nearly enough experience with this concurrency model in Python to have a good sense of what primitives will be best. I’m definitely open to the many possibilities. However, I was trying to be cautious about starting out with primitives that are familiar from other concurrency models, thus why I was avoiding a dedicated per-interpreter lock type.

Yeah, this would be an absolutely interesting idea to explore. See PEP 554 for many other similar ideas.

1 Like

This is awesome, thanks :pray:! I’m going to play around with this soon.

Separately, as this just came up in the other thread, do you have any thoughts about whether extension packages will start supporting subinterpreters anytime soon? Or what would be needed to help with that?

1 Like

The key bit of complexity is migrating static globals to per-interpreter state without introducing significant performance regressions. Extension modules that expose C APIs (either directly or via capsules) also face additional challenges.

Beyond the module-specific issues, I haven’t kept track of whether or not Cython, SWIG, pybind11, etc support emitting modules that use multi-phase init with full subinterpreter support.

1 Like

Well… looking at the C++ source code generated by Cython for a sample project, it seems that multi-phase init and module state are mutually exclusive (either CYTHON_PEP489_MULTI_PHASE_INIT or CYTHON_USE_MODULE_STATE can be enabled, not both).

So you get things like:

#if CYTHON_PEP489_MULTI_PHASE_INIT
static PyObject* __pyx_pymod_create(PyObject *spec, PyModuleDef *def); /*proto*/
static int __pyx_pymod_exec_lib(PyObject* module); /*proto*/
static PyModuleDef_Slot __pyx_moduledef_slots[] = {
  {Py_mod_create, (void*)__pyx_pymod_create},
  {Py_mod_exec, (void*)__pyx_pymod_exec_lib},
  {0, NULL}
};
#endif

#ifdef __cplusplus
namespace {
  struct PyModuleDef __pyx_moduledef =
  #else
  static struct PyModuleDef __pyx_moduledef =
  #endif
  {
      PyModuleDef_HEAD_INIT,
      "lib",
      0, /* m_doc */
    #if CYTHON_PEP489_MULTI_PHASE_INIT
      0, /* m_size */
    #elif CYTHON_USE_MODULE_STATE
      sizeof(__pyx_mstate), /* m_size */
    #else
      -1, /* m_size */
    #endif
      __pyx_methods /* m_methods */,
    #if CYTHON_PEP489_MULTI_PHASE_INIT
      __pyx_moduledef_slots, /* m_slots */
    #else
      NULL, /* m_reload */
    #endif
    #if CYTHON_USE_MODULE_STATE
      __pyx_m_traverse, /* m_traverse */
      __pyx_m_clear, /* m_clear */
      NULL /* m_free */
    #else
      NULL, /* m_traverse */
      NULL, /* m_clear */
      NULL /* m_free */
    #endif
  };
  #ifdef __cplusplus
} /* anonymous namespace */
#endif
#endif

I frankly don’t understand what the implications are, as the module initialization changes are always confusing to me. Perhaps a Cython developer (@scoder, @da-woods) is available to comment.

Cython deliberately errors if a loaded module is imported in a different interpreter.
AFAICT PyO3 does the same.