2
votes

So, since several days I do a lot of research about multiprocessing and multithreading on python and i'm very confused about many thing. So many times I see someone talking about GIL something that doesn't allow Python code to execute on several cpu cores, but when I code a program who create many threads I can see several cpu cores are active.

1st question: What's is really GIL? does it work? I think about something like when a process create too many thread the OS distributed task on multi cpu. Am I right?

Other thing, I want take advantage of my cpus. I think about something like create as much process as cpu core and on this each process create as much thread as cpu core. Am I on the right lane?

2
For GIL see this thread: stackoverflow.com/questions/1294382/…Oli
In Python, threads all run on one cpu core sharing one Python interpreter instance. The GIL (Global Interpreter Lock) generally prevents multiple threads from executing at the same time. Multiprocessing involves running two or more separate processes, each with its own Python interpreter, so there's no need for the GIL to prevent concurrent execution — but there's also no shared memory, so there's a lot more overhead.martineau
@martineau Of course, in the multiprocessing case you still have the GIL preventing concurrent execution among threads, but now have separate GILs for each interpreter and everything is still single-threaded.Booboo
@Booboo: I never said there wasn't — just contrasting multithreading vs multiprocessing. In the latter, while still single-threaded, it doesn't matter because concurrent processing is still occurring among the multiple separate processes.martineau
@martineau I was just clarifying the point for the newbie. I also do no not believe it is true that all threads run on one core, although the GIL most (but not all) of the time renders the availability of extra cores of little value.Booboo

2 Answers

6
votes

To start with, GIL only ensures that only one cpython bytecode instruction will run at any given time. It does not care about which CPU core runs the instruction. That is the job of the OS kernel.

So going over your questions:

  1. GIL is just a piece of code. The CPython Virtual machine is the process which first compiles the code to Cpython bytecode but it's normal job is to interpret the CPython bytecode. GIL is a piece of code that ensures a single line of bytecode runs at a time no matter how many threads are running. Cpython Bytecode instructions is what constitutes the virtual machine stack. So in a way, GIL will ensure that only one thread holds the GIL at any given point of time. (also that it keeps releasing the GIL for other threads and not starve them.)

Now coming to your actual confusion. You mention that when you run a program with many threads, you can see multiple (may be all) CPU cores firing up. So I did some experimentation and found that your findings are right (which is obvious) but the behaviour is similar in a non threaded version too.

def do_nothing(i):
    time.sleep(0.0001)
    return i*2

ThreadPool(20).map(do_nothing, range(10000))
def do_nothing(i):
    time.sleep(0.0001)
    return i*2

[do_nothing(i) for i in  range(10000)]

The first one in multithreaded and the second one is not. When you compare the CPU usage by by both the programs, you will find that in both the cases multiple CPU cores will fire up. So what you noticed, although right, has not much to do with GIL or threading. CPU usage going high in multiple cores is simply because OS kernel will distribute the execution of code to different cores based on availability.

Your last question is more of an experimental thing as different programs have different CPU/io usage. You just have to be aware of the cost of creation of a thread and a process and the working of GIL & PVM and optimize the number of threads and processes to get the maximum perf out.

You can go through this talk by David Beazley to understand how multithreading can make your code perform worse (or better).

3
votes

There are answers about what the Global Interpreter Lock (GIL) is here. Buried among the answers is mention of Python "bytecode", which is central to the issue. When your program is compiled, the output is bytecode, i.e. low-level computer instructions for a fictitious "Python" computer, that gets interpreted by the Python interpreter. When the interpreter is executing a bytecode, it serializes execution by acquiring the Global Interpreter Lock. This means that two threads cannot be executing bytecode concurrently on two different cores. But this is also what prevents these two threads from interfering with one another when they are updating the same list, for example. This also means that true multi-threading is not implemented. But does this mean that there is no reason to use threading? No! Here are a couple of situations where threading is still useful:

  1. For certain operations the interpreter will release the GIL, i.e. when doing I/O. So consider as an example the case where you want to fetch a lot of URLs from different websites. Most of the time is spent waiting for a response to be returned once the request is made and this waiting can be overlapped even if formulating the requests has to be done serially.
  2. Many Python functions and modules are implemented in the C language and are not limited by any GIL restrictions. The numpy module is one such highly optimized package.

Consequently, threading is best used when the tasks are not cpu-intensive, i.e. they do a lot of waiting for I/O to complete, or they do a lot of sleeping, etc.