Thread starvation while locking in a loop in Python

It seems that this is due to OS thread scheduling. My guess is that either OS gives very high priority to cpu intensive threads (whatever that means) or chosing a next thread to acquire the lock (done by the OS) takes more time than actually acquiring the lock by the second thread. Either way not much can be deduced without knowing the internals of the OS.

But it's not GIL since this code:

#include <mutex>
#include <iostream>
#include <chrono>
#include <thread>

std::mutex mutex;

void my_thread() {
    int counter = 100;
    while (counter--) {
        std::lock_guard<std::mutex> lg(mutex);
        std::this_thread::sleep_for(std::chrono::milliseconds(500));
        std::cout << "." << std::flush;
    }
}

int main (int argc, char *argv[]) {
    std::thread t1(my_thread);
    auto start = std::chrono::system_clock::now();
    // added sleep to ensure that the other thread locks lock first
    std::this_thread::sleep_for(std::chrono::milliseconds(1000));
    {
        std::lock_guard<std::mutex> lg(mutex);
        auto end = std::chrono::system_clock::now();
        auto diff = end - start;
        std::cout << "Took me " << diff.count() << std::endl;
    }
    t1.join();
    return 0;
};

which is just a C++11 version of your code gives exactly the same result (tested on Ubuntu 16.04).


Multithreading in CPython is somewhat complicated. To make the implementation (of memory management among other things) easier, CPython has a built-in "Global Interpreter Lock". This lock ensures that only one thread at a time can be executing Python bytecode.

A thread will release the GIL when it does I/O or reaches into a C extension. And if it doesn't the GIL will be taken from it up at certain intervals. So if a thread is busy spinning like your thread is, at one point it will be forced to give up the GIL. And you would expect that in that case another thread gets a chance to run. But because Python threads are basically operating system threads, the OS also has a say in scheduling. And there a thread that is constantly busy may get a higher priority and so get more chances to run.

For a more in-depth look, check out the video understanding the Python GIL by David Beazley.