I’d like to explore this point a little. In my experience, the cases where the GIL has been a problem for me are where a single-threaded solution is too slow to be practical, and the GIL makes multithreading no faster. So removing the GIL won’t “just expose threading bugs I already have”, as I don’t already have any code. In that situation, my problem is knowing whether code I’d naturally write is correct at all. And threading bugs like race conditions are often non-obvious, so I could easily just end up with subtly wrong answers, not easy-to-spot crashes.
The following use case is far from critical, but I think it illustrates the point nicely. Suppose I want to calculate the probability of rolling a total on some dice, by simulation:
def sum_ndm(n, m):
return sum(random.randint(1, m) for _ in range(n))
def calculate(n, m, count=100_000):
pmf = Counter(map(lambda _: sum_ndm(n, m), range(count)))
return pmf
def calculate_threaded(n, m, count=100_000):
with ThreadPoolExecutor() as executor:
pmf = Counter(executor.map(lambda _: sum_ndm(n, m), range(count)))
return pmf
This is a trivial conversion of a single-threaded approach to a thread pool, and if the single-threaded approach was unusably slow (for example, a count of a million, and a more complicated “calculate the result” function than sum_ndm
), it’s very likely what I would use. Does it have any race conditions? I don’t know. And if I’m using it to estimate probabilities that I don’t know up front, I wouldn’t be able to say the answers “looked wrong”.
There’s no global state here, so my instinct says this is safe. But am I right?
And what if instead of using a Counter
I wanted to store the results in a data structure? For simplicity, say I had a global results
dictionary, and sum_ndm
was modified to
results = {}
def sum_ndm(n, m):
result = sum(random.randint(1, m) for _ in range(n))
results.get(result, 0) += 1
Is that safe? A developer not familiar with the collections
module might pick this approach first.
This is where I imagine nogil tempting users into writing unsafe (i.e., wrong) code, and I’m not as comfortable as some people seem to be that classifying the problem with pure Python code as “just exposing bugs your code already has” or “not needing any extra expertise in threading”.
To be clear, this is not artificial - it’s a simplified example of a genuine real-world problem that I absolutely would have used threading for if the GIL hadn’t prohibited it. And I would have boasted to a friend about how Python made solving this really easy, compared to the complicated C++ program that they wrote to do the calculation fast (but single threaded). So we’re talking high stakes here - do I get publicly embarrassed by writing incorrect code?