SWI-Prolog Python interface
All Application Manual Name SummaryHelp

  • Documentation
    • Reference manual
    • Packages
      • SWI-Prolog Python interface
        • Introduction
        • Data conversion
        • Janus by example - Prolog calling Python
        • library(janus): Call Python from Prolog
        • Calling Prolog from Python
        • Janus and threads
          • Calling Prolog from a Python thread
          • Python and Prolog deadlocks
        • Janus and signals
        • Janus versions
        • Janus as a Python package
        • Prolog and Python
        • Janus performance evaluation
        • Python or C/C++ for accessing resources?
        • Janus platforms notes
        • Compatibility to the XSB Janus implementation
        • Status of Janus

6 Janus and threads

Where SWI-Prolog support native preemptively scheduled threads that exploit multiple cores, Python has a single interpreter that can switch between native threads.7Actually, you can create multiple Python interpreters. It is not yet clear to us whether that can help improving on concurrency. Initially the Python interpreter is associated with the thread that created it which, for janus, is the first thread calling Python. The Prolog thread that initiated Janus may terminate. This does not affect the embedded Python interpreter and this interpreter may continue to be used from other Prolog threads.

Janus ensures it holds the Python GIL when interacting with the Python interpreter. If Python calls Prolog, the GIL is released using Py_BEGIN_ALLOW_THREADS.

  • Multiple Prolog threads can make calls to Python. The access to Python is serialized. If a Prolog thread does not want other threads to use Python it can use py_with_gil/1. When multiple Prolog threads make many calls to Python performance tends to drop significantly.

  • Multiple Python threads can make calls to Prolog. While Prolog is working on the query, the Python interpreter may switch to other Python threads.

6.1 Calling Prolog from a Python thread

Prolog may be called safely from any Python thread. The Prolog execution is embraced with Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS, which implies that Python is allowed to switch to another thread while Prolog is doing its work.

If the calling Python thread is not the one that initiated Janus, janus.query_once() and janus.query() attach and detach a temporary Prolog engine using PL_thread_attach_engine() and PL_thread_destroy_engine(). This is relatively costly. In addition we allow associating a Prolog engine persistently with the calling thread.

int janus.engine()
Return the identifier of the Prolog engine associated to the current thread, -1 if no engine is attached or -2 if this version of Prolog does not support engines.
int janus.attach_engine()
Attach a Prolog engine to the current thread using PL_thread_attach_engine(). On success, return the integer thread id of the created Prolog engine.8The current implementation passes NULL to PL_thread_attach_engine(). Future versions may provide access to the creation attributes.

If the thread already has an engine the attach count is incremented and the current engine id is returned. The engine is detached after a matching number of calls to janus.detach_engine()

None janus.detach_engine()
Decrement the attach count of the attached Prolog engine. Destroy the engine if this count drops to zero. Raises an exception of the calling thread is not attached to a Prolog engine.

6.2 Python and Prolog deadlocks

In a threaded environment, Python calls must be guarded by PyGILState_Ensure() and PyGILState_Release() that ultimately lock/unlock a mutex. Unfortunately there is no PyGILState_TryEnsure() and therefore we may create deadlocks when Prolog locks are involved. This may either apply to explicit Prolog locks from with_mutex/2 and friends or implicit locks on e.g. I/O streams. The classical scenario is thread A holding the Python GIL and wanting to call Prolog code that locks a mutex M, while thread B holds M and wishes to make a Python call and this tries to lock the GIL. The predicate py_gil_owner/1 can be used to help diagnosing such issues.