Powerful, flexible, and programmer-friendly, Python is widely used for everything from web development to machine learning. By the two most-cited measures, Python has even surpassed the likes of Java and C to become the most popular programming language of all. After years of soaring popularity, Python might well seem unstoppable.
But Python faces at least one big obstacle to its future growth as a programming language. It's called the GIL, the global interpreter lock, and Python developers have been trying to remove it from the default implementation of Python for decades now.
Although the GIL serves a critical purpose, namely ensuring thread safety, it also creates a serious bottleneck for multithreaded programs. In short, the GIL prevents Python from taking full advantage of multiprocessor systems. For Python to be a first-class language for concurrent programming, many believe the GIL has to go.
[...] Strictly speaking, the global interpreter lock isn't part of Python in the abstract. It's a component of the most commonly used Python implementation, CPython, which is maintained by the Python Software Foundation.
[...] What makes the GIL such a problem? For one, it prevents true multithreading in the CPython interpreter. That makes a whole class of code accelerations—optimizations that are readily available in other programming languages—far harder to implement in Python.
[...] The problem, as you might guess, is that getting rid of the GIL is far easier said than done. The GIL serves an important purpose. Its replacement must not only ensure thread safety but fulfill a number of other requirements besides.
[...] The first formal attempts to ditch the GIL date as far back as 1996, when Python was at version 1.4. Greg Stein created a patch to remove the GIL, chiefly as an experiment. It worked, but single-threaded programs took a significant performance hit. Not only was the patch not adopted, but the experience made it clear that removing the GIL was difficult. It would come at a whopping developmental cost.
[...] Whether Python makes the GIL optional, adopts subinterpreters, or takes another approach, the long history of efforts and experimentation shows there is no easy way to remove the GIL—not without huge development costs or setting Python back in other ways. But as data sets grow ever larger, and AI, machine learning, and other data processing workloads demand greater parallelism, finding an answer to the GIL will be a key element to making Python a language for the future and not just the present.
(Score: 1, Interesting) by Anonymous Coward on Tuesday February 28, @11:40PM (11 children)
This is probably naive since I don't know anything about Python's internals, but it seems like there should be some immutable objects in there you can share with no problem, then just create multiple instances of the interpreter and add locks and/or message-passing to the language.
Aside from that, isn't optimizing an interpreter kind of a turd-polishing exercise anyway? If you need speed, compile.
(Score: 0) by Anonymous Coward on Tuesday February 28, @11:45PM (1 child)
lol
(Score: 1, Interesting) by Anonymous Coward on Wednesday March 01, @01:57AM
Or just use Perl to do it: https://metacpan.org/pod/IPC::Shareable [metacpan.org]
Threads aren't that important (unless you're using stuff like Windows) - in most cases you don't need to share everything or even most stuff, you actually only need to share a smaller subset of stuff.
Not sure what Python's equivalent is - the Google results didn't look that great to me.
(Score: 2) by rigrig on Wednesday March 01, @10:09AM
Often you just need something to be fast enough.
No one remembers the singer.
(Score: 3, Insightful) by Immerman on Wednesday March 01, @02:47PM (6 children)
I'm not even a Python programmer, but...
Sharing immutable objects isn't the problem, and aside from string constants there's rarely that many anyway. But for multi-threading to be useful the threads need to communicate, which means they need to share mutable objects. E.g. a common one is a shared queue where one thread puts "work orders" in, and other worker threads pull them off to perform the tasks specified. In which case the queue must be implemented as a thread-safe data structure - which imposes a huge performance penalty whenever they're accessed.
Many (most?) thread-aware languages handle that situation by treating all variables as non-thread-safe by default, and leaving it up to the programmer to declare them as thread-safe where needed, use thread-safe container classes, etc. Of course that does mean stealthy threading bugs creep in anywhere they forget to do so... but it also completely removes the thread-safe penalty from everything else, and is really easy for the compiler developers.
However, since Python doesn't use variable declarations that's not really an option, plus it's supposed to be safe and easy programming, which means you really shouldn't count on the programmer catching 100% of the variables (and other situations) that need to be thread-safe anyway... which I suspect adds up to either having to treat *all* variables as thread-safe, or avoid true multi-threading entirely.
(Score: 2) by Freeman on Wednesday March 01, @03:15PM (5 children)
I think I ended up going down this particularly crazy rabbit hole in python, when I was trying to get a music file to play, but also be able to use the GUI. I think I ended up using pygame (just for the audio mixer) and some thread handling as well. So I could stop the music, crazy idea, I know. Looking back at my more recent fiddling, I just dumped tried to use multi-threading and just used the pygamer mixer. Because multi-threading was a serious hassle and probably way overkill for what I needed. Also, I'm not a serious programmer, so I was probably doing plenty wrong.
Joshua 1:9 "Be strong and of a good courage; be not afraid, neither be thou dismayed: for the Lord thy God is with thee"
(Score: 2) by turgid on Wednesday March 01, @06:27PM (4 children)
Instead of multiple threads, you could have tried multiple processes. The GUI would be running in one and the audio stuff in another (in a different instance of the Python interpreter). You'd need some sort of message queue with either sockets or shared memory to get them to communicate with a simple little protocol.
I refuse to engage in a battle of wits with an unarmed opponent [wikipedia.org].
(Score: 2) by Freeman on Wednesday March 01, @07:02PM (3 children)
I know I looked at multi-process and multi-thread. I'm also pretty sure I needed a lot more thought to go into it, before I really understood what in the world I was doing. Using the mixer from pygame was much simpler, at least for me.
Joshua 1:9 "Be strong and of a good courage; be not afraid, neither be thou dismayed: for the Lord thy God is with thee"
(Score: 2) by turgid on Wednesday March 01, @08:20PM (1 child)
...and the correct solution! I was just trying to think how you could solve the problem in Python otherwise.
I refuse to engage in a battle of wits with an unarmed opponent [wikipedia.org].
(Score: 3, Informative) by Freeman on Wednesday March 01, @09:34PM
Yeah, I did go down that path initially, but found pygame's mixer and it just worked. Which is what I wanted and I figured pygame would be good enough for what I was doing. Maybe not the best solution, but certainly a standard solution, at least to my thinking.
Joshua 1:9 "Be strong and of a good courage; be not afraid, neither be thou dismayed: for the Lord thy God is with thee"
(Score: 2) by turgid on Wednesday March 01, @08:23PM
I've written a few parallel shell scripts over the years. Being the shell, and being so high level and simple, you just fire up a new shell process for each thing you want to do concurrently. It's cheap, quick, safe, robust and surprisingly not as inefficient as you'd imagine. When using such a high level language and running lots of external commands/programs, the process overhead is pretty insignificant.
I refuse to engage in a battle of wits with an unarmed opponent [wikipedia.org].
(Score: 3, Interesting) by sgleysti on Wednesday March 01, @03:14PM
100% agree. I do hardware stuff now, but I wrote something in C++ at work, and a software guy tells me, "Why C++? You should have used Python, it would be so much easier." As if the language alone makes something hard or easy. My response was that compiling to machine code, strong typing, and support for multithreading are all good things.
I honestly find scripting languages to be incredibly wasteful if they attempt to do anything computationally intensive.
(Score: 3, Interesting) by tangomargarine on Tuesday February 28, @11:42PM
First citation:
Python is apparently in the top 4.
Second citation:
I think somebody has a slightly different definition of "unstoppable" than me.
"Is that really true?" "I just spent the last hour telling you to think for yourself! Didn't you hear anything I said?"
(Score: 0) by Anonymous Coward on Tuesday February 28, @11:43PM (2 children)
So is it some kind of crutch so your shitty script doesn't take over the machine as it consumes all memory and cpu until it crashes?
(Score: 5, Insightful) by maxwell demon on Wednesday March 01, @04:08AM (1 child)
No. It is a way to make the interpreter thread-safe by ensuring it doesn't actually multithread.
The Tao of math: The numbers you can count are not the real numbers.
(Score: 2) by driverless on Thursday March 02, @08:02AM
And TFA is asking entirely the wrong question, the answer to "is it time to remove the Great Big Lock" is always, always, not just "yes" but "hell yes!", but the real question is "how TF do we actually get rid of the Great Big Lock"?
Google "lock-free programming" if you want to get an inkling of just how hard this is, let alone trying to retrofit it onto a massively complex decades-old code base.
(Score: 3, Insightful) by Snotnose on Wednesday March 01, @12:45AM (11 children)
Never used threading in Python, but I've done a lot of IPC and threading in other languages (C and Java).
I fail to see why GIL exists at all. If you're going to go threaded then you get to deal with all the fun quirks the language throws at you. Just having someone say "yeah, no, that's too hard for you" doesn't seem like the way to go.
I just passed a drug test. My dealer has some explaining to do.
(Score: 5, Informative) by HiThere on Wednesday March 01, @01:15AM (5 children)
It exists because back when Python was first designed, multiple CPU computers were very rare, and those that existed usually only had two processors, so it was BETTER to only run on one processor. And designing single threaded code is a lot simpler, so that's what was done. (It was easier and there wasn't any downside.)
When Python3 was being designed, there were already so many changes that nobody wanted to add any more. So Python3 was released with a global interpreter lock. If I've got my timing right, that's about when the Linux kernel was being rewritten to remove its large scale locks.
Now time has passed, and there are LOTS of even cheap computers with multiple CPUs. So it's become important to remove the GIL, but to remove it without breaking legacy code is difficult. However Python is still recovering from the Python2 to Python3 transition, and breaking legacy code is something that really needs to be avoided.
Javascript is what you use to allow unknown third parties to run software you have no idea about on your computer.
(Score: 4, Informative) by ikanreed on Wednesday March 01, @05:42AM (2 children)
The power of pedantry compels me to inform you a processor isn't the same as a core.
(Score: 2) by turgid on Wednesday March 01, @07:45AM
True, but to someone programming in user space they're pretty much one and the same unless you start talking about machines with multiple sockets and tens or hundreds of "cores." Even then, if you're running something like Linux, you can think of each one as a separate CPU. The difference is in memory latency. What's behind what caches and memory controllers? Which memory banks are connected to which socket? To the programmer, a process is a process and a thread is a thread. Where they get their time slice is entirely up to the OS.
I refuse to engage in a battle of wits with an unarmed opponent [wikipedia.org].
(Score: 2) by HiThere on Wednesday March 01, @02:45PM
Well, I wanted to exclude GPUs. I suppose I could have said, a processor, but not a specialize one, but that's really verbose.
Javascript is what you use to allow unknown third parties to run software you have no idea about on your computer.
(Score: 2) by driverless on Thursday March 02, @08:42AM (1 child)
Wait, there's a Python 3? When did that come out? May need to update some of our code then...
(Score: 0) by Anonymous Coward on Thursday March 02, @04:32PM
December 2008 dude
(Score: 3, Insightful) by sjames on Wednesday March 01, @02:11AM (4 children)
The GIL exists because the various data structures are not thread safe. Thread safety can be a HUGE performance drag if it's not very carefully designed.
Note that a lot of C programs are written as single threaded because most programmers find threaded programming hard to get right. Or they're written with very limited threading, often just for (some) I/O operations.
It's worth noting that CPython can take advantage of threads for I/O as well. When writing C extensions that will wait for system calls, you can release the GIL as long as you re-take it before calling any Python interpreter functions. Sometimes it's even worth it if the C function makes a local copy of the data outside of the interpreter data structures to do lengthy computation, then re-takes the GIL to copy the data into a return value.
Locking can be a challenge for many reasons. Too fine grained and they do more damage to performance than the benefits of multi-threading can make up for. Too coarse and you're not really better off than having a GIL. Missing a case where a lock is needed or failing to take a necessary lock and you get heisenbugs. In really bad cases with fine grained locking you get a deadly embrace.
And, of course, if service threads are a useful design, it may be feasible use fork, pipes, and data marshaling to take advantage of multiple cores.
One first pass at improving the situation for Python might be a class that hides the marshaling, unmarshaling, and pipes under the rug. I believe that can be accomplished without altering the GIL at all.
(Score: 3, Interesting) by PiMuNu on Wednesday March 01, @11:03AM (3 children)
It's worth saying that for me, the first and most value of python is that it obeys the rule (haha) "Do no evil". In particular, python should never crash or hang or break for "Evil" reasons, like failure to allocate or deallocate memory/etc. Heisenbugs should be avoided. The CPU overhead is well worth the shortened development time.
A lot of multithreaded stuff can hang or break for "Evil" reasons (e.g. deadlock), so it needs to be handled carefully. If they start introducing Heisenbugs into python, it dilutes the chief benefit of python.
(Score: 2) by sjames on Wednesday March 01, @04:21PM (2 children)
Fully agreed. Personally, I would be more interested in fast IPC, possibly including better facilities for sharing memory between processes than in making Python threading better.
Even in C or other languages where there is no GIL issue, programs that fork with limited shared memory pools or that marshal data through pipes are a lot less likely to do 'evil' things than fully threaded programs. Honestly, threading should be the last resort no matter how good the programmer is.
Really, that's nothing more than an extension to the idea that in a multi-developer project, code should be designed to a strong internal API and keep variables as local as possible rather than flinging global variables around.
Even where a global variable makes sense in a single threaded application, it should have very few well defined and documented writers.
(Score: 2) by PiMuNu on Wednesday March 01, @04:58PM (1 child)
I guess os.fork already does share memory between the forked processes - but assigns new memory if a variable is changed. Is that the sort of thing you have in mind? Would be nice if os.fork could return more than just an integer - i.e. better inter process comms... maybe there is some way to do it, IIRC there is a multiprocess module but I don't think last time I used it there was much capability for this sort of thing.
(Score: 2) by sjames on Wednesday March 01, @10:51PM
There's a wide variety of options available between fork and thread. For shared memory, I'm thinking about creating an array, struct, or array of structs that is read/write for both parent and child process. One good way to do that is to open an anonymous file, then fork and both parent and chile mmap that file, giving them a shared memory.
The multiprocessing module supports using pipes for communication (or in Windows, the less elegant MS API). Under the hood (at least in POSIX environments) it probably does something like: call os.pipe, fork. Parent closes one end and communicates on the other. Child opens and communicates on the opposite endponts. I have done that before in Python without resorting to calling C code.
The shared memory through an anonymous file can probably be implemented using the ctypes module.
(Score: 4, Funny) by Anonymous Coward on Wednesday March 01, @02:38AM
While they're removing the GIL, would they mind removing the rest of the language as well? kthxbye
(Score: 2) by DannyB on Wednesday March 01, @03:32PM (1 child)
Just a suggestion.
Introduce a synchronization mechanism.
In Java a short section of code can 'synchronize' on some object. That particular object now has a 'lock'. Any other thread that does a synchronize on that same object will be blocked until the first object exits its synchronize block. Many objects may synchronize on the same object and all but one of them will block. Once one exits that section of code, exactly one other blocked thread will proceed.
Now this is like the GIL, but on a specific object. Only threads interested in synchronizing on a specific queue or other structure will block. It is not a global lock. Other queues and thread safe dictionaries or other structures could each individually have their own separate locks.
Like Java, Pythons should make the mechanism part of the language spec with specific behavior guarantees across all platforms. This includes the memory model. If two threads read or write a member in an object, they should see specific guaranteed predictable behavior on all platforms.
synchronize(foo) {
...do some quick minimal stuff while foo is locked...
}
Only one syncronize(foo) section of code can ever be active. It can be the same actual code, or multiple portions of your code can do a synchronize on foo.
How often should I have my memory checked? I used to know but...
(Score: 2) by DannyB on Wednesday March 01, @03:35PM
I mentioned the memory model because of CPU caches. It is more important to have a guaranteed memory model for the programmer. Bugs cost money. Developer time costs money. Maintenance costs money. Cpu cycles and bytes are cheap and getting cheaper.
How often should I have my memory checked? I used to know but...
(Score: 1) by shrewdsheep on Wednesday March 01, @04:15PM (6 children)
I am a recent Perl -> Python convertee (AI and stuff) and give or take the languages are very similar. One major design flaw in python IMO is the call-by-reference semantics. Not only does it contribute to buggy code but it makes multi-threading harder. Well, we are out of good scripting languages as Perl has entered legacy mode.
(Score: 2, Informative) by Anonymous Coward on Wednesday March 01, @08:12PM (1 child)
Have you checked Julia language?
(Score: 2) by RamiK on Thursday March 02, @12:42PM
I was about to ask the same thing.
compiling...
(Score: 2) by turgid on Wednesday March 01, @08:25PM (3 children)
I thought Perl just changed its name when it went to v6? Or has that died a death too?
I refuse to engage in a battle of wits with an unarmed opponent [wikipedia.org].
(Score: 2, Informative) by shrewdsheep on Wednesday March 01, @08:38PM (2 children)
It's called Raku now and it's a complex language and development lacks momentum. Most importantly, the libraries aren't there. Lack of bindings for modern libraries was my primary reason for leaving Perl (still maintaining my own legacy code base). Also recent changes in Perl has started to break older code of mine (being on perlbrew now).
(Score: 2) by turgid on Friday March 03, @03:08PM (1 child)
It was complex enough before. What have they done to it?
I refuse to engage in a battle of wits with an unarmed opponent [wikipedia.org].
(Score: 1) by shrewdsheep on Friday March 03, @04:21PM
Every conceivable concept was folded into the language which made it incredibly complex: Formal OO (which Perl lacks, modeled after the bolt-on framworks like Moose which is ugly). lazy evaluation (infinite lists), parsing (smooth transition between regexes and full grammars), concurrency, macros, and that's just what I can recall. The parser part is actually well designed, however, I still prefer the wonderful Parse::RecDescent. The wish to cover everything is unfortunately the downfall of Raku/perl6. Had Perl added proper function signatures and OO in time, it would still be the strongest scripting language now. The emergence of PHP should have been the wakeup call.