137

The global interpreter lock (GIL) seems to be often cited as a major reason why threading and the like is a touch tricky in Python - which raises the question "Why was that done in the first place?"

Being Not A Programmer, I've got no clue why that might be - what was the logic behind putting in the GIL?

Fomite
  • 2,616
  • 6
  • 18
  • 20
  • 11
    The [Wikipedia article](http://en.wikipedia.org/wiki/Global_Interpreter_Lock) states that *"the GIL can be a significant barrier to parallelism—a price paid for having the dynamism of the language"*, and goes on to say that *"Reasons for employing such a lock include: increased speed of single-threaded programs (no necessity to acquire or release locks on all data structures separately), and easy integration of C libraries that usually are not thread-safe."* – Robert Harvey Feb 13 '13 at 01:17
  • 4
    @RobertHarvey, Dynamism has nothing to do with it. The problem is mutation. – dan_waterworth Jun 13 '13 at 12:00
  • http://stackoverflow.com/questions/265687/why-the-global-interpreter-lock – Ciro Santilli OurBigBook.com Sep 10 '15 at 10:04
  • 1
    Can't help feeling that like Java's lack of unsigned numerics, it was intended to prevent people who don't know what they're doing shooting themselves in the foot. Unfortunately, anyone who _does_ know what they're doing gets a deficient language, which is a real shame because Python rocks in so many other ways – Basic Oct 06 '15 at 21:36
  • @Basic I get that unsigned values are handy at a low-level (would frequently use them for embedded C code), but at a higher level they usually seem like a hyper-optimization. Maybe if you're doing some bit-twiddling...what's your use-case? – Nick T Oct 02 '16 at 15:45
  • @NickT Well, the most obvious is on the Raspberry Pi when interacting with hardware but I've also had issues with cryptography and hashing. – Basic Oct 02 '16 at 16:12
  • 1
    @Basic there has to be some standard way to deal with byte arrays in Java (I haven't used it in a long time) in order to do crypto math. Python (for example) doesn't have signed numbers, but I wouldn't even try to do bitwise ops with it because there are better ways. – Nick T Oct 02 '16 at 23:16
  • Check out [Understanding the Python GIL](https://www.dabeaz.com/python/UnderstandingGIL.pdf). – Petr Vepřek Apr 06 '19 at 08:49
  • ...and the [talk](https://www.youtube.com/watch?v=Obt-vMVdM8s). – Petr Vepřek Apr 06 '19 at 09:03

3 Answers3

123

There are several implementations of Python, for example, CPython, IronPython, RPython, etc.

Some of them have a GIL, some don't. For example, CPython has the GIL:

From http://en.wikipedia.org/wiki/Global_Interpreter_Lock

Applications written in programming languages with a GIL can be designed to use separate processes to achieve full parallelism, as each process has its own interpreter and in turn has its own GIL.

Benefits of the GIL

  • Increased speed of single-threaded programs.
  • Easy integration of C libraries that usually are not thread-safe.

Why Python (CPython and others) uses the GIL

In CPython, the global interpreter lock, or GIL, is a mutex that prevents multiple native threads from executing Python bytecodes at once. This lock is necessary mainly because CPython's memory management is not thread-safe.

The GIL is controversial because it prevents multithreaded CPython programs from taking full advantage of multiprocessor systems in certain situations. Note that potentially blocking or long-running operations, such as I/O, image processing, and NumPy number crunching, happen outside the GIL. Therefore it is only in multithreaded programs that spend a lot of time inside the GIL, interpreting CPython bytecode, that the GIL becomes a bottleneck.

Python has a GIL as opposed to fine-grained locking for several reasons:

  • It is faster in the single-threaded case.

  • It is faster in the multi-threaded case for i/o bound programs.

  • It is faster in the multi-threaded case for cpu-bound programs that do their compute-intensive work in C libraries.

  • It makes C extensions easier to write: there will be no switch of Python threads except where you allow it to happen (i.e. between the Py_BEGIN_ALLOW_THREADS and Py_END_ALLOW_THREADS macros).

  • It makes wrapping C libraries easier. You don't have to worry about thread-safety. If the library is not thread-safe, you simply keep the GIL locked while you call it.

The GIL can be released by C extensions. Python's standard library releases the GIL around each blocking i/o call. Thus the GIL has no consequence for performance of i/o bound servers. You can thus create networking servers in Python using processes (fork), threads or asynchronous i/o, and the GIL will not get in your way.

Numerical libraries in C or Fortran can similarly be called with the GIL released. While your C extension is waiting for an FFT to complete, the interpreter will be executing other Python threads. A GIL is thus easier and faster than fine-grained locking in this case as well. This constitutes the bulk of numerical work. The NumPy extension releases the GIL whenever possible.

Threads are usually a bad way to write most server programs. If the load is low, forking is easier. If the load is high, asynchronous i/o and event-driven programming (e.g. using Python's Twisted framework) is better. The only excuse for using threads is the lack of os.fork on Windows.

The GIL is a problem if, and only if, you are doing CPU-intensive work in pure Python. Here you can get cleaner design using processes and message-passing (e.g. mpi4py). There is also a 'processing' module in Python cheese shop, that gives processes the same interface as threads (i.e. replace threading.Thread with processing.Process).

Threads can be used to maintain responsiveness of a GUI regardless of the GIL. If the GIL impairs your performance (cf. the discussion above), you can let your thread spawn a process and wait for it to finish.

Martijn Pieters
  • 14,499
  • 10
  • 57
  • 58
Md Mahbubur Rahman
  • 4,747
  • 5
  • 32
  • 38
  • 58
    Sounds like sour grapes to me. Python can't do threads properly, so you make up reasons why threads are unnecessary or even bad. "If the load is low, forking is easier", seriously? And the GIL is "faster" for all those cases only if you insist on using referencce-counting GC. – Michael Borgwardt Feb 13 '13 at 09:38
  • 11
    `s/RPython/PyPy/g`. @MichaelBorgwardt Giving reasons pro GIL is kind of the point of the question, isn't it? Though I would agree that some of the contents of this answer (namely discussion of alternatives) is beside the point. And for better or for worse, refcounting is now almost impossible to get rid of -- it is deeply ingrained in the entire API and code base; it's almost impossible to get rid of it without rewriting half the code and breaking *all* external code. –  Feb 13 '13 at 13:35
  • 13
    Don't forget the `multiprocessing` library - standard since 2.6. It's worker pools are a super-slick abstraction for some simple types of parallelism. – Sean McSomething Feb 13 '13 at 18:52
  • @MichaelBorgwardt It's not an excuse because nobody knows how to add multi-core threads to Python. Way back when, someone *did* implement a fine-grained locking scheme in CPython, but it wasn't kept. This was in part due to significant overhead and in part for maintainability and correctness reasons. Plus, reference counting isn't the only cause of slowness - fine-grained locking is going to be slow on an interpreter as naïve as CPython, reference counting or not. – Veedrac Aug 04 '15 at 14:53
  • 1
    @MichaelBorgwardt The multiprocessing approach to multitasking that Python employs is indeed both safer and easier in most situations than the nightmare bugs that can arise from threading. Python's multiprocessing support is at a higher level than most other languages'. – alcalde Aug 05 '15 at 02:44
  • 9
    @alcalde Only if you don't know what you're doing and/or you don't want your threads to be able to work cooperatively/communicate. Otherwise, it's a royal pain in the backside, especially considering the overhead of launching a new process on some OSes. We have servers with 32 cores, so to utilise them fully in CPython I'd need 32 processes. That's not a "good solution" it's a hack to work around CPython's inadequacies. – Basic Oct 06 '15 at 21:40
  • 9
    The fact that threads exist on platforms other than Windows should be proof enough that forking isn't adequate in every situation. – zneak Oct 14 '15 at 18:55
  • 2
    @Basic Python has provisions to communicate between processes and it's far less a pain than the vast number of subtle race conditions and other bugs that can result from shared memory. What's wrong with having 32 processes? PostgreSQL uses processes and not threads, and it's been shown to scale linearly (on reads) up to at least 64 cores. Multiprocessing and message passing is a parallel paradigm and hardly a hack. It's been used on supercomputing clusters for decades. In fact, Guido has talked about Python being used on a 64K core supercomputer! – alcalde Oct 15 '15 at 20:10
  • 1
    @alcalde Well if Guido says it's ok, it must be fine... Multiprocessing on windows has a much higher overhead than on linux, or perhaps that just doesn't count? Also, see if you can get the same perfomance on (say) a large merge sort using multiple processes as you can using multiple threads on a non-GIL interpreter. I'm not saying multiprocessing doesn't have its place - it does - but it's not the right answer to every problem, no matter how much some "pythonistas" want it to be. – Basic Oct 15 '15 at 20:24
  • @Basic Who'd use a Windows system for high performance anyway? – Zelphir Kaltstahl Nov 22 '15 at 21:37
  • @Zelphir so you consider anything that needs multiple threads "high performance"? As to who would use it on windows, millions of people without a chip on their shoulder / religious zealotry for with their OS. Windows is excellent in certain circumstances, awful in others. Same applies linux, OSX and pretty much everything else. – Basic Nov 22 '15 at 21:49
  • @Basic merely using your own argument. If it wasn't highperformance, then why is the whole GIL such a problem, when you can use multiprocess? That'll be good enough if it doesn't need to be the highest of performance. If it is about highperformance, then why would anyone run it on Windows? It doesn't make sense to blame the GIL when you could use multiprocess, only because Windows can't deal. – Zelphir Kaltstahl Nov 22 '15 at 23:07
  • Ok, we're getting off-topic but... How would you go about implementing a merge sort efficiently in python? On linux or windows? If the language has limitations, don't go blaming the OS for not covering up the inadequacies. In any case, shall we delete these comments to remove clutter? – Basic Nov 22 '15 at 23:12
  • @Basic I built a very CPU intensive program in Python using multiprocessing, that eats though 32 cores on a Windows server daily. I wish it was done using C/Unix/multithreading instead, but as far as development time and reliability go Python is pure gold. – Muposat Jan 08 '16 at 07:27
  • @Muposat I don't disagree with you - one of the reasons I'm unhappy with Python's threading is that I'm in a similar situation - we have a multiprocessing queue consumer that often maxes out the CPU - and it works great as long as there's no dependencies and little communication between workers. However, giving all processes access to shared mutable variables is nigh impossible if performance is a consideration. Not a problem for a queue consumer but a real hinderance elsewhere. – Basic Jan 08 '16 at 08:10
  • 1
    Would you mind explaining `It is faster in the multi-threaded case for cpu-bound programs that do their compute-intensive work in C libraries.` That's the one point I'm still trying to figure out. – Jorge Bucaran Jul 05 '16 at 17:08
  • 1
    @JorgeBucaran It's not that it's faster per-se, it's that the c libraries ignore the GIL, so there's no performance hit for the C code itself, and the c code can release the GIL so python code can continue to execute. In short, this is a special case that allows true multi-threading but only because all but one thread is outside Python's control. – Basic Jul 21 '18 at 12:30
47

First off: Python doesn't have a GIL. Python is a programming language. A programming language is a set of abstract mathematical rules and restrictions. There is nothing in the Python Language Specification which says that there must be a GIL.

There are many different implementations of Python. Some have a GIL, some don't.

One simple explanation for having a GIL is that writing concurrent code is hard. By placing a giant lock around your code, you force it to always run serially. Problem solved!

In CPython, in particular, one important goal is to make it easy to extend the interpreter with plugins written in C. Again, writing concurrent code is hard, so by guaranteeing that there will be no concurrency, it makes it easier to write extensions for the interpreter. Plus, many of those extensions are just thin wrappers around existing libraries which may not have been written with concurrency in mind.

Jörg W Mittag
  • 101,921
  • 24
  • 218
  • 318
  • 7
    That's the same argument as Java's lack of unsigned numerical types - the developers think everyone else is dumber than they are... – Basic Oct 06 '15 at 21:42
  • 1
    @Basic - believe it or not, even when you're not really, really dumb, it turns out that having a language that makes simplifying assumptions that mean you don't think about certain things in order to make them work is still a useful thing. CPython is great for certain things, including simple multithreaded applications (where the program is IO bound, which many are, and therefore the GIL doesn't matter), because the design decisions that made the GIL the best solution also make programming those applications easier, particularly the fact that it supports *atomic operations on collections*. – Jules Jul 21 '18 at 10:15
  • @Jules Yes, it's very handy right up until you need those capabilities. cpython's "preferred" solution of "just write it in another language like c++" then means you lose every singly python benefit. If you're writing half your code in c++, then why start from Python? Sure, for small API/glue projects it's quick and easy, and for ETL it's second to none, but it's not suitable for anything that requires heavy lifting. Same as using Java to talk to hardware... It's almost comical the hoops you have to jump through. – Basic Jul 21 '18 at 12:23
  • @Basic One of Python's and thus to an extend CPython's core philosophies is to make the technology "friendly and easy to use". Parallel programming without global lock is _not_ that. Considering that there are many implementations without GIL, it makes sense to at least provide one implementation that has it. – Nearoo Dec 15 '19 at 17:11
  • You say "it makes sense to at least provide one implementation that has it." like it's the obvious conclusion, but no other language I'm aware of hobbles its developers in this way, so it can't be _that_ obvious. – Basic Dec 15 '19 at 19:19
  • @Basic: "no other language I'm aware of hobbles its developers in this way" – As I explained in my answer, this has *nothing to do with the language*. There is absolutely nothing in the Python Language Specification about a GIL, and the overwhelming majority of Python implementations do not have anything remotely like a GIL. In fact, there is *only one* implementation of Python that has it. And this is very similar to other languages. E.g. in Ruby, there is *one* implementation which has what they call a "GVL" (Global VM Lock), all other implementations don't. I believe the situation is the … – Jörg W Mittag Aug 03 '20 at 12:02
  • … same for PHP. I am not sure about Java, but the Java Language Specification surely allows it. (It specifies what happens when Java code runs concurrently, but it does not specify that it *has to* run concurrently at all.) And I wouldn't be surprised if there existed a Java implementation that does not allow Java code to run concurrently. C doesn't even support concurrency *at all* without the use of external runtimes and external libraries that are not part of C. – Jörg W Mittag Aug 03 '20 at 12:04
18

What is the purpose of a GIL?

The CAPI documentation has this to say on the subject:

The Python interpreter is not fully thread-safe. In order to support multi-threaded Python programs, there’s a global lock, called the global interpreter lock or GIL, that must be held by the current thread before it can safely access Python objects. Without the lock, even the simplest operations could cause problems in a multi-threaded program: for example, when two threads simultaneously increment the reference count of the same object, the reference count could end up being incremented only once instead of twice.

In other words, the GIL is prevents corruption of state. Python programs should never produce a segmentation fault, because only memory safe operations are permitted. The GIL extends this assurance to multi-threaded programs.

What are the alternatives?

If the purpose of the GIL is to protect state from corruption, then one obvious alternative is lock at a much finer grain; perhaps at a per object level. The problem with this is that although it has been demonstrated to increase the performance of multi-threaded programs, it has more overhead and single-threaded programs suffer as a result.

dan_waterworth
  • 7,287
  • 2
  • 34
  • 45
  • 2
    It would be great to let a user run a program with an interpreter option replacing the gil for fine-grained lock, and somehow know -in a readonly way- whether the current process was raised with or without gil. – Luis Masuelli Dec 17 '14 at 22:17
  • Despite GIL I managed to produce a segmentation fault in a multithreaded program due to careless use of module pyodbc. Thus "should never produce a segmentation fault" is a fallacy. – Muposat Jan 08 '16 at 07:35
  • @Muposat the use of "should" (rather than "will") is the key phrasing that saves that sentence from being a fallacy. Ironclad guarantees are difficult to come by. That said, I don't think I've seen the Python interpreter segfault more than once or twice in the 10+ years I've been relying on it, so it comes pretty close. – Jeremy Friesner Jan 20 '22 at 21:50