question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Conditional `with gil` and threading

See original GitHub issue

A nogil block with a loop that uses an unlikely with gil statement e.g. approximately,

cpdef float test_gil(float[:] x):
    cdef int i = 0
    cdef int size = x1.shape[0]
    cdef float out = 0
    with nogil:
        while i < size:
            out_sum += x[i]
            if i > size +1:
                with gil:
                    raise ValueError
   return out 

appears work similarly to the while function not releasing gil, when using in multi-threaded code.

See more complete minimal example in https://github.com/scikit-learn/scikit-learn/pull/17038#issuecomment-619476846

Naively I though that since the with gil is never executed, performance wise this would be equivalent to the same function without the with gil block (or roughly that threading performance would be directly impacted by the fraction of run time where the gil is released). However it does not seem to be the case.

Is this behavior expected? If so maybe the documentation section https://cython.readthedocs.io/en/latest/src/userguide/external_C_code.html#acquiring-and-releasing-the-gil could be updated to mention conditional acquiring of GIL.

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

2reactions
scodercommented, Apr 26, 2020

I think the best solution is to get rid of the try-finally node and do the cleanup in FuncDefNode.generate_function_definitions(), acquiring the GIL only when necessary for a given exit case (error or success).

2reactions
scodercommented, Apr 26, 2020

Note that you do not need to write the with gil in this specific example since it’s implicit in the raise statement (i.e. you can raise exceptions also from within nogil sections). That generates the same code as with gil, though, so will run into the same problem.

Looking at the C code that Cython generates from the example that you linked to, the difference is that the function that uses with gil has a Python temp variable, which needs to be cleaned up at the end in the exception case. For that, it needs to acquire the GIL. However, it wouldn’t need the GIL in the success case, and still acquires it. That makes it uselessly hammer on the GIL in very fast functions like this.

The GIL acquisition is unconditionally inserted here, as a try-finally statement wrapping the whole function body: https://github.com/cython/cython/blob/f09e61ab721ad51526ec7a6798fc01d8346f539d/Cython/Compiler/ParseTreeTransforms.py#L1861-L1872

and gets released here, after generating the function code: https://github.com/cython/cython/blob/f09e61ab721ad51526ec7a6798fc01d8346f539d/Cython/Compiler/Nodes.py#L2145-L2148

I don’t know how difficult it would be to detect the needless acquire and avoid it, but in any case, I consider it a bug that this happens unconditionally. in the success case, it should run through the function without touching the GIL at all.

Read more comments on GitHub >

github_iconTop Results From Across the Web

the GIL and its effects on Python multithreading
The GIL allows only one OS thread to execute Python bytecode at any ... the gil->cond conditional variable to notify GIL-awaiting threads ......
Read more >
Can you race condition in Python while there is a GIL?
My understanding is that due to the Global Interpreter Lock (GIL) in cPython, only one thread can ever be executed at any one...
Read more >
What Is the Python Global Interpreter Lock (GIL)?
Python's Global Interpreter Lock or GIL, in simple words, is a mutex (or a lock) that allows only one thread to hold the...
Read more >
Multithreading in Python with Example: Learn GIL in Python
A race condition occurs when multiple threads access or modify a shared resource at the same time. It can be avoided by Synchronizing...
Read more >
What is the Python Global Interpreter Lock (GIL)
Since the CPython's memory management is not thread-safe, the GIL ... what a race condition is, let's consider a threading example where we ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found