Fasting threads while locking in a loop in Python

I have an application that gets locked in a loop in one thread in order to perform some kind of task. There is also a second thread that also wants to get a lock from time to time. The problem is that this second thread hardly gets the opportunity to do its job, since the first is almost always blocked by the first. I hope the following code clarifies what I'm trying to say:

import time from threading import Lock, Thread lock = Lock() def loop(): while True: with lock: time.sleep(0.1) thread = Thread(target=loop) thread.start() before = time.time() lock.acquire() print('Took {}'.format(time.time() - before)) 

If the application print you will notice that it needs more than 0.1 s. But sometimes it happens that he just waits endlessly. I tested this in both Python 2.7.11 and Python 3.4.3 on Debian Linux 8, and it works the same.

This behavior is counterintuitive to me. In the end, when the lock.acquire lock lock.acquire in the loop , lock.acquire already waiting for it to be released, and it should immediately receive the lock. But instead, it looks like the loop gets the lock first, although he did not expect it to be released at the time of release at all.

The solution I found is to sleep between each iteration of the loop in an unlocked state, but this does not seem to me an elegant solution and does not explain to me what is happening.

What am I missing?

+9
source share
3 answers

This seems to be related to scheduling OS threads. I believe that any OS gives a very high priority to threads of an intensive processor (whatever that means) or , to select the next thread to get a lock (made by the OS), it takes longer than actually acquires a lock on the second thread. In any case, not much can be deduced without knowledge of the internal components of the OS.

But this is not a GIL with 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 version of your C ++ 11 code, gives exactly the same result (tested on Ubuntu 16.04).

+5
source

Multithreading in CPython is somewhat complicated. To simplify implementation (memory management), CPython has a built-in Global Interpreter Lock. This lock ensures that only one thread at a time can execute Python bytecode.

A GIL will be released in the stream when it works with I / O or reaches extension C. And if it does not, the GIL will be taken from it at regular intervals. Therefore, if a thread is busy spinning like your thread, at some point it will be forced to abandon the GIL. And you expect that in this case another thread will get a chance to start. But since Python threads are primarily operating system threads, the OS also has a say in planning. And there, a thread that is constantly busy can get a higher priority, and therefore get a better chance of starting.

For a deeper look, check out David Beasley's video understanding of Python GIL .

+2
source

To add a useful answer to @freakish, the practical solution for me was to add a tiny dream right before the greedy thread gets the lock. In your case:

 def loop(): while True: # give time for competing threads to acquire lock time.sleep(0.001) with lock: time.sleep(0.1) 

Sleeping for 0 seconds (as suggested in the comments) did not work for me.

0
source

All Articles