String parsing which tokenised in-place. DNS calls which used static buffers. Things which exploited Vax specific stack behaviour.
I think the GIL has been a blessing and a curse.
String parsing which tokenised in-place. DNS calls which used static buffers. Things which exploited Vax specific stack behaviour.
I think the GIL has been a blessing and a curse.
Around that time, doing cross-platform C++, I got an early look at Java, with concurrency built in from the start, along with GC and various other nice features that were easier to use than C++, and I "knew" it was going to be huge. (But who knew that the MIS people would take over Java, when it seemed clearly targeted at non-MIS programmers, and now MIS people are stuck with the C++ syntax and verbosity, after coming from 4GLs, etc.)
Then mainstream programmers picked up Python, which, IIRC, originally was an embeddable extension language, which was why it was simple. And for which the GIL made more sense.
And Java doesn't even have good (conceptual) support for concurrency. Compared with eg Erlang, Rust or even Haskell.
But Java was still better at it than C or C++ at the time.
One can make a solid argument that modern Java is adequate. But I don't buy you believe that of early Java?
Still way after Java was released I guess
When one really does need shared mutable state, Haskell supports transactional updates to mutable state (STM), with optimistic locking and rollbacks. It's awesome, but unfortunately just not practically possible in any language with rampant untracked side-effects.
I would say that in some ways this is simply incomparable to java but I think this concurrency model is probably the easiest and simplest to work with of any concurrency model.
There are obviously drawbacks like with anything but for a lot of situations it's an excellent choice.
That's not even a contest. Java was released in 1995. At that time C did not have any kind of support for concurrency, all solutions were platform dependend and not part of the language.
hint:
% cd /usr/include
% ag --no-color '[^A-Z]_REENTRANT'I suppose I could just dismiss the comment, but curiosity got the better part of me. I'm glad someone asked. I also had to search what "4GL" means. I'm assuming this top Wikipedia hit [1] is what the OP meant.
I don't know if it's reasonable to expect people to know these terms. But, the concise text is not saving anyone beyond the OP any appreciable amount of time, so what's its value?
[1] https://en.m.wikipedia.org/wiki/Fourth-generation_programmin...
Python was originally a teaching language, to replace BASIC and Pascal for kids and beginners.
(Yes, there once was a time when people took teaching programming seriously.)
> The letter “B” was chosen because it is the first letter of the word “beginner” and because the project was meant to become a language for teaching programming to absolute beginners.
[1] https://inference-review.com/article/the-origins-of-python
Yes, van Rossum was an implementer of ABC, which was a teaching language. Python drew on that experience, but also from Modula-2 and Modula-3, and the needs for writing an extension language for the Amoeba operating system.
From the initial release notes at https://www.tuhs.org/Usenet/alt.sources/1991-February/001749... :
[Python is] an extensible interpreted programming language that
combines remarkable power with very clear syntax.
This is version 0.9 (the first beta release), patchlevel 1.
Python can be used instead of shell, Awk or Perl scripts, to write
prototypes of real applications, or as an extension language of large
systems, you name it.
No mention of it being a teaching language.And between C++ and Rust coroutines, still not sure which one I like less.
I, myself, am not a Go guy, but I feel it has to be mentioned here. Go's approach might not be as universal as C++'s or Rust's but I think for a large number of use cases it makes sense.
There is a best practices guideline from one of the ASP.NET architects, https://github.com/davidfowl/AspNetCoreDiagnosticScenarios/b...
During last year they researched adding Go/Java's approach to .NET, but now it is too late. See the ASP.NET Q&A session at BUILD 2023.
Aside from being too late (already having two models and not wanting to add a third) they also mention that the Go/Java approach adds a performance penalty when calling native APIs.
For example, you can't safely use print in a Python signal handler, because the entire I/O system is not reentrant [1], yet the documentation shows doing this in a "how to do it properly" example [2] while explaining that it's horribly unsafe to throw from a signal handler, which is the default for SIGINT. (And also the reason why the threading module doesn't expose PyThreadState_SetAsyncExc). So despite appearances to the contrary (since the Python signal handler is invoked by the VM, it doesn't actually run in a signal handler context; the signal module registers a C handler which simply sets a flag that is checked by the VM every instruction), you should probably only do in a Python signal handler what you would do in C, which is very little. Set a flag or write to a pipe. Don't go around and call library functions.
[1] E.g. https://bugs.python.org/issue24283 [2] https://docs.python.org/3/library/signal.html#note-on-signal...
No one's flipping a switch and breaking mountains of sketchy C.
I suppose the need to run your app in "no-GIL" mode is less than needing to jump from 2.7 to 3.
It also seems like the latter isn't meant as a replacement (for the moment), but rather as an option.
stat = rt_oldFunctionName(&newStateArg, oldarg[,...])
mostly (again from memory. I'd not live or die by this)Entirely different from 2->3.
That analogy is so poorly applicable it's laughable.
"Dur dur this <insert anything here> is just like 2 -> 3."
HN had always been susceptible to drive-by ignorant comments, but it's reaching new levels.
There's literally nothing of substance to the suggestion. (And if you think there is, present an actual informed argument.)
They were explicitly suggesting something by asking that question.
Subtext is hard to prove, but that doesn't make it not real.
Programmers and project managers on the other hand...
> We want to be very careful with backward compatibility. We do not want another Python 3 situation …
* update: I have also lived through 10 years of Python 2-3. It was not pretty.
If the burden becomes too big, at some point people may find it is the easier option to switch to a different ecosystem. I hope not.
That's by far the most unpleasant language I've coded in.
How does one library support both GIL and no-GIL?
Easy, it supports no-GIL, so it supports both. Done.
This “that’s not how it went” stuff going down is quite just blatant historical revisionism.
Maybe it’s a good idea? Maybe it’s not?
…but anyone down voting “this reminds me of the Python 2/3” fiasco has no idea what they’re talking about.
That’s not whining; it’s just an observation that the committee making these decisions gives zero ducks about the impact this will have for anyone other than the handful of vested parties involved in making the decisions.
Pypi has what, 500k projects on it? Many abandoned.
Whom exactly is going to update those?
Or do packages get an automatic “doesn’t work with no-GIL” unless the author explicitly opts to enable it?
Or do we live in a future where any package, with any dependency may or may not have undefined behaviour in no-GIL mode?
Like, sure… it’s a good change for many people… once all the hard work is done by the community.
Does that remind you of anything?
Mmm.
They do recognize the impact on the ecosystem will be huge, they recognize there will be at least 5 years of parallel gil/nogil versions(*), and they say they don't want a 2-3 story all over again.
Yet they have decided against their own best advise (to avoid such a situation).
I find it utterly confusing.
(*) not just one parallel version, there will be 2 for every release following the introduction. In reality organisations will have to maintain 4-6 different baselines of Python releases along with a matching (and likely differing) set of libraries. I don't mean to fearmonger, I just happen to maintain such environements and I know the effort that goes into this first hand.
> “but now I have to rewrite my library because some people might use it in non-GIL mode”.
Yes, if library maintainers want their library to remain relevant, they will need to accomodate what the languages developer community uses. This is true for all languages. If they don't want to, that's okay, the community will come up with new libraries.
> the committee making these decisions gives zero ducks about the impact this will have
If they were giving zero ducks, they wouldn't make it backwards compatible, nor would there be a command line option to control the behavior.
>Pypi has what, 500k projects on it? Many abandoned. > >Whom exactly is going to update those?
Languages that base decisions on the update behavior, or lack thereof, of library maintainers, effectively freeze themselves.
And why exactly is the update behavior of abandoned packages a problem? They are abandoned anyway.
> Like, sure… it’s a good change for many people… once all the hard work is done by the community.
The people who want to get rid of the GIL are part of the Python development community. Many of them are library developers themselves.
Where is this demand exactly? We hear a lot of complaining but very often this is due to a lack of awareness of available (& often better) alternatives to threading.
There is a very small number of use cases that will benefit from free threading.
The motivation summary of PEP-703 contains some material on this:
https://peps.python.org/pep-0703/#motivation
Further discussions going back years can be found with a brief search. This discussion is almost as old as Python3.
> due to a lack of awareness of available (& often better) alternatives to threading.
Such as?
There are exactly 2: asyncio, which is useless for CPU/GPU bound workloads, and multiprocessing with all the pain of relying on expensive spawns, expensive and limited IPC and the joy of having to orchestrate across process boundaries.
Guess what the most common advice is for dealing with CPU bound parallelisation problems in Python? "Use another language". Guess what all the languages recommended (C, C++, Rust, Go, Java) have in common? They have thread-based parallelism.
> There is a very small number of use cases that will benefit from free threading.
Basically any workload that is CPU bound, which in the day and age of giant data aggragation and running huge ML models at scale is more important than every before, is a use case for this.
Right, because the whole point of Python is as a glue language for native libraries. Adding multi threading to Python is like adding to to Bash.
I have had this precise need before. I have a multi-threaded native library. The multi-threading is essential for reasonable performance in the intended use-case for the library. But my users can't write C++ to call it; they'd like Python. They'd like to extend certain specific operations that my native library does during processing. I give them Python bindings. The perf gained by running the library on multiple cores is completely negated whenever multiple C++ threads of my native library need to run Python code.
1. But they don't "have to".
2. Even if they did, why would downvoting be "disingenuous"?
Most of them don't contain C extensions.
> Whom exactly is going to update those?
Its developers of course. Many are presumably watching PEP-703, others will require lobbying by their users. But there is no rush to do so because...
> Or do packages get an automatic “doesn’t work with no-GIL” unless the author explicitly opts to enable it?
> Or do we live in a future where any package, with any dependency may or may not have undefined behaviour in no-GIL mode?
... the interpreter will indeed fall back to enable the GIL if an extension is loaded that doesn't declare support of the no-GIL mode. Additionally, some extensions are actually safe to use without GIL as long as their Python APIs prevent concurrent access to them and thus act like the GIL themselves. In these cases, the interpreter can be forced to run without the GIL.
Isolating the C extension to another process is another possibility to reduce the impact on applications where all others can already run without the GIL. Actually, multiprocessing is already a common approach in the Python ecosystem for concurrent and parallel processing.
> Whom exactly is going to update those?
> once all the hard work is done by the community.
If I want to use Python with parallel threads in the app I'm building, I don't need to wait for every last one of those 500k packages. I can wait for only the packages I'm using, or risk it and force Python to run in nogil mode anyway. It's my choice.
> Or do packages get an automatic “doesn’t work with no-GIL” unless the author explicitly opts to enable it?
As other comments say in this thread: yes.
> Does that remind you of anything?
> Mmm.
2-to-3 burned you. We get it. You might be getting flashbacks to that nightmare. That is understandable. But you need to look beyond a surface level similarity — "That was a transition. This is a transition. They're identical!" — and at the actual transition itself. The problems of 2-to-3 aren't present here. The user is not forced to choose between two incompatible options. Library authors aren't forced to migrate their code forward. Users and library authors do not need to collectively choose one version over another. The newer version remains compatible with older code. The sky is not falling.
Is this some kind of joke? Do we live in clown world now? You do realize that a lock is a trivial primitive in multi threading?
The concept of a "little bespoke GIL" is ridiculous. A lock is a lock. Sure, an extension could just put a global lock on every extension call and then you do end up with an extension wide lock but every other extension remains unaffected. There is no intelligence or genius behind putting a global lock in the interpreter that somehow gets ruined by putting the lock in the extension. In fact, GIL is the dumbest decision you could make and everything from that point on can only get better, not worse.
Testing the difference between running it with and without `nogil` without having to install 2 different interpreters.
Testing libraries during transitions.
Simply giving users a choice.
Convenience.
Almost no one uses pre-Go.1.11 (GOPATH instead of Modules) any more for project organisation, and the transition is trivially easy. And yet, the toolchain still reacts to `GO111MODULE=off`.
Historically, Napoleon lost. That has zero impact on whether I get a promotion.
The 2 changes simply have nothing to do with each other. No-GIL python can still run GIL code, it just won't be able to run it in parallel.
The problem -- as you point out -- with 2 -> 3 was that supporting both versions was very difficult. Because Python 2 couldn't run Python 3 code (and vice versa).
And thus libraries existed in awkward states for years.
But GIL can run no-GIL code. Supporting both is no harder than supporting one of those options (the no-GIL one).
No, none of them do, or at least none that I’ve ever seen in 4 years of doing rust full-time.
And I'm sure someone will drive by, now, and tell us we have to use xyz, "obviously". But if that's not "obviously" what I find in an Internet search, then the community has apparently not agreed on it consistently in a sustained way.
https://scala-cli.virtuslab.org/
The thing that really struck me after years of python is how it lets you out dependencies directly in a comment on top of a script and it will download and run with them automatically, without poisoning any system settings. It's so simple!
The old-style nix-shell command in Nix can do this[1] for every language and package Nixpkgs supports, although it’s not that often used because it ties your shebangs to Nix. (An equivalent feature for the new CLI is a work in progress[2].)
[1] https://nixos.org/manual/nix/stable/command-ref/nix-shell.ht...
My point lies more in the install experiences for say, a command line tool written in said language. For Java I need one Java install on my system, it’s the most recent one. I install it and then all the old code just works, all my Java command line tools just work, I never have to watch homebrew install some stupid version of Java@11 when I have Java@13 installed like I do python@3.9 when I know I already have python@3.10. All of this is BECAUSE people basically threw up their hands and distribute the core language at their version with their code and their chosen dependencies “installed” into it… so we’ve all got 15 bespoke versions of the same language kicking around on our hard drives. That is really stupid.
I wonder if our definition of "non-trivial" might differ if neither of these apply.
Things like `map[k] = v` would be atomic both before and after nogil, and things like `map[k] += 1` are not atomic even with GIL, the read and store can be split from each other.
....I believe so.
E.g. there's no plan to make lists, dicts, thread unsafe.
Or you make the extra effort of being thread safe and you can declare your library as not requiring the GIL.
Now if a user script mixes your GIL free lib with an older lib that has not been updated, well, too bad for them. Even with your hard work, the code will still operate like before, everything gets the GIL treatment.
Normal python devs will need to track down which pesky dependency of their script is causing the GIL slowdown. Kinda sucks but at least nothing breaks.
It's a sensitive, opt-in, and safe way forward. Hard to argue against it, really..
> In --disable-gil builds, when loading an extension, CPython will check for a new PEP 489-style Py_mod_gil slot. If the slot is set to Py_mod_gil_not_used, then extension loading proceeds as normal. If the slot is not set, the interpreter pauses all threads and enables the GIL before continuing. Additionally, the interpreter will issue a visible warning naming the extension, that the GIL was enabled (and why) and the steps the user can take to override it.
Why then is there even a need for a seperate nogil build? If it is like this says, wouldn't it be easier to just make the standard build switch to gil or nogil automatically (or honor the users choice).
The fact that the SC thinks having two versions suggests there is more complexity involved than this section of the PEP leads readers to believe.
On top of that, some of the list and dictionary operations are available to extensions as C macros to avoid the overhead of a C function call.
However, it looks like the nogil build will be able to run in "GIL mode" for maximum compatibility, including switching to GIL mode partway through execution, but I'm expecting this to be slower than running the gil build in GIL mode.
Yes, that's what they were asking about. Why have two versions for a mode switch. Everything you explained before that is irrelevant, I'm afraid.
> but I'm expecting this to be slower than running the gil build in GIL mode.
That could be the answer to their question, but that's not definitive enough.
2. There are a lot of users that want fast Python and a lot of effort was put in to optimise it. I think the core team wouldn't want to release and expect everybody to use a version which regresses in performance, for a feature (nogil) which most won't be able to use due to using C extensions which haven't had nogil-supporting code changes.
It looks for a flag, if it doesn't see it then it turns the GIL on.
Where is the need for recompilation?
> I think the core team wouldn't want to release and expect everybody to use a version which regresses in performance
But you're just guessing that it's slower in GIL mode, aren't you?
https://docs.python.org/3/c-api/typeobj.html#c.PyObject.ob_r...
The stable ABI lets extensions access the reference count of any object directly. I don't know why. Normally the functions Py_IncRef and Py_DecRef should be sufficient. Objects no longer have a single number as their reference count.
Edit: In Python 3.2-3.9, the stable ABI included Py_INCREF, the C macro.
https://docs.python.org/3/c-api/type.html#c.PyType_FromModul...
The stable ABI lets extensions create new Python types. These can override tp_alloc field to use custom memory allocators when instances of the type are instantiated. The custom memory allocator needs to initialise the reference count to 1.
> But you're just guessing that it's slower in GIL mode, aren't you?
There are new implementations with per-object locks of list.append, dict.__setitem__ etc. These are incredibly common operations in Python code. These inherently will be more complicated than the previous implementation, meaning more instructions and slower. So there needs to be a branch at the start of list.append of whether to go to the old gil implementation or the new nogil one. Adding a branch so frequently will inherently make the runtime slower.
Now with a lot of work these can be optimised and the speed penalty reduced, but CPython goes on an annual release cycle. If the nogil code is held as a fork of the CPython repository without being merged in, until it reaches a performance goal, that brings other issues.
Does that stay true once the GIL turns back on?
> These can override tp_alloc field to use custom memory allocators when instances of the type are instantiated. The custom memory allocator needs to initialise the reference count to 1.
I'm not following why this affects ABI compatibility, sorry.
> So there needs to be a branch at the start of list.append of whether to go to the old gil implementation or the new nogil one. Adding a branch so frequently will inherently make the runtime slower.
I don't know about that. CPUs handle always-taken and never-taken branches very well.
In the current nogil implementation, AFAICS, it seems the GIL can't be turned back on so there is no answer yet.
Theoretically, you could have a one-off operation which fixes all objects when the GIL is turned on. However, there's no way to get all objects in Python. gc.get_objects() only returns tracked objects, and there is no way to list untracked objects.
It seems, three fields are exposed on every CPython object in the stable ABI, any change which affects their offsets will break the stable ABI. https://github.com/capi-workgroup/problems/issues/4#issuecom...
> I'm not following why this affects ABI compatibility, sorry.
True, PyType_FromSpec can set tp_alloc to a wrapper function which papers over the difference in what the "allocfunc" should initialise the memory to.
Re performance - merging the change is the only way people will actually start targeting nogil.
and dynamic patching is also an option.
In either case it seems obvious now that there is likely no point bothering with the stable ABI any more.
Something nobody though about lurks in this nogil build, and we are only going to find out when it goes to prod and users report about it.
Instead of breaking the entire Python community's code, the risk is opt-in for a few years.
Ugh, Nomenclature is hard
Because non-GIL compatible doesn't mean GIL incompatible
Surely, unattended-upgrades should NOT be bumping major versions?
Long term the plan is to 100% remove the GIL from python.
From the article:
> Long-term, we want no-GIL to be the default, and to remove any vestiges of the GIL (without unnecessarily breaking backward compatibility). We don’t want to wait too long with this, because having two common build modes may be a heavy burden on the community (as, for example, it can double test resources and debugging scenarios), but we can’t rush it either. We think it may take as much as five years to get to this stage.
Under base assumptions it also says:
> We want to be very careful with backward compatibility. We do not want another Python 3 situation, so any changes in third-party code needed to accommodate no-GIL builds should just work in with-GIL builds (although backward compatibility with older Python versions will still need to be addressed).
We're now up to 128 core CPUs, and even cheap CPUs have 6 cores on them. Restricting things to a single core's performance gets more and more limiting as time passes.
In Python you have whole C modules with global state. Load 10 of them, add the interpreter complexity and soon enough no one knows what is going on any more.
As it is, most developers (including core devs!) don't even bother to check for memory leaks. I don't think they'll run tsan, and if they do, it will be on a small test suite that only covers 10% of the code.
Given the software development practices in Python and especially in the AI space, I'm very pessimistic about this feature.
I also suspect that the GIL has saved us from debugging reentrant and/or dangerously concurrent code for years, and I salute the GIL for forcing us to build Arrow for IPC in Python, in particular.
Someday, URI attributes in CPython docstrings might specify which functions are constant time, non re-entrant, functions.
Reentrancy (computing): https://en.wikipedia.org/wiki/Reentrancy_(computing)
Global Interpreter Lock: https://en.wikipedia.org/wiki/Global_interpreter_lock
Even writing a JVM native JNI library would have allowed to avoid a lot of that pain (and the library would have been useable from Clojure, Kotlin , Scala, JRuby[1], Jython[2], Java, etc) without any painful threading issues.
[1] which I’m aware of having been used in production by companies in the past
[2] which I’m aware has been quite a bit under maintained for the last several years
I don’t think it’s false to say that the ease of moving hot spots into C is part of the reason Python has been so successful for thirty years.
I hope this works, but I am very sceptical about being able to port code that worked with a locking solution provided for the enthusiast trying out C working without running into concurrency bugs.
I distinctly remember that during the time period where Python grew from “minor” to “dominate” (roughly 1995-2005), doing Python dev was often a process of answering “are there bindings for that?” And usually _there were_, because of that tooling.
- The history is backwards: it isn’t that devs wanted to make a native library and then chose Python, it’s that they chose Python and then they needed a native library.
- Python works very well for scripts and small programs, and decently well for medium-size programs. This makes libraries with concrete purposes very useful and productive. If your library, say, helps devs do some basic calculation with time series, the ability to be used in quick scripts is a big plus.
- The C API is fairly good, and libraries such as pybind11 make it even better. You don’t need a lot of code or boilerplate for an extension.