I’ve often thought that proper threading should be learned by the masses, not to be scared of it. This sort of statement does nothing but spread FUD.
Alternatively, message-passing isn't traditional trading, but Erlang/Go-style languages are another way to approach concurrency or parallelism.
Rust does nothing to magically prevent race conditions for you. Rust safety does not encompass race conditions, starvation, etc.
What it does is defensively prevent you from sharing mutable variables without explicitly opting for it.
It's good for catching careless sharing issues, but not much more.
I also struggle to understand the threading model of COM and C libraries like libusb (https://libusb.sourceforge.io/api-1.0/libusb_mtasync.html), though that might just be me, and each library tends to have a different threading model. Rust's Send/Sync is a 90% solution which you can learn upfront, is checked by the compiler, and applies to all libraries and works for most use cases.
Right, I can see the kind of codebase you're referring to.
I don't see Rust as a magical weapon solving concurrency issues though. Namely because Rust (the compiler) has a very limited view of what happens in the lifetime of a multithreaded system, and no view at all of the lifetime of a multiprocess system.
Even when writing purely single threaded Rust, you quickly end up having to let go of the strictly static memory sharing checks and switch to dynamic ones.
I have yet to find a use case where Rust solves anything but the most blatant synchronization issues.
Qt signals/slots across threads is also message passing. Transparent to boot. Too bad you need to afford a lawyer on retainer to use it these days.
It’s like saying that C is a safe language, just “get good” at it.
There are safe alternatives such as structured concurrency.
... or at least it should be public knowledge that anything prefixed with "async" is windows 3.1 multitasking from the 90s...
Why are we getting 64+ core servers to run single threaded cooperative multitasking on them? :)
If you don't care about CPU performance, instead of threads you should go for an event-loop approach (see asyncio in Python). As soon as you have threads (on a language-level, not implementation-level), there is some notion of implicit switching, and you run into issues. So, the language you're looking for is JavaScript, which is single-threaded and every context switch is explicit (in form of `await` or `yield`).
Like, for example,
count += 1
---The GIL primarily protects C code, not Python code.
The author literally says: "This was actually pretty hard to discover. The first few experiments failed, because Python is pretty smart about when it runs each thread."
But I think the main way the GIL protects inexperienced users is that its presence has the effect that Python code using the threading APIs is very uncommon, since those APIs don't currently provide parallel computation (only parallel IO). So inexperienced users are protected due to 99% of Python programs not using multithreading as a result (and so likely the one they're developing also doesn't), whereas this will presumably change when the limitations imposed by the GIL go away.
> The author literally says: "This was actually pretty hard to discover. The first few experiments failed, because Python is pretty smart about when it runs each thread."
I completely disagree here with your reading. Rare race conditions are much worse than races you trip over frequently.
All of the multithreading bugs without any of the multithreading performance.
You should hear about async...
;)
It's simply not "sane multithreading" not being able to run Python (byte)code concurrently. It's just a huge annoyance.
> because it might lose its beginner friendlyness
Python is not beginner friendly. It has one of the worst documentations out there. Also it does things very different compared to other languages (C#, PHP, Java, JS, ...) - I would advise anyone against learning Python as their first language, while Python is my favorite language.
> And Python already has an expert mode - called Cython oer Numba to name just two.
CPython is the "official" Python. How is it beneficial to depend on some 3rd party projects? I don't want to use such projects.
As to the PEP itself: It's optional, with the GIL being enabled by the default. So what's the problem...
Please read my post again. I am advocating a GIL per-thread model with an explicit feature for sharing selected objects. This allows for all cores concurrency, essentially like free threading yet with safeguards. I like to call that a sane way because it builds on decades of research and industry experience of the software engineering community.
> As to the PEP itself: It's optional, with the GIL being enabled by the default.
It's not optional if you build tools and libraries that need be able to run with both gil and no-gil.
Is there a PEP or something written about it? I don't like this idea at first glance. Feels like another hack. Though I have to admit it sounds better than the annoying multiprocessing approach.
> It's not optional if you build tools and libraries that need be able to run with both gil and no-gil.
Why do they need to? A library dev is free not support one or the other? If a library then does not provide the preferred mode of the user, then that's bad, but that's life. Ideally most libraries will support a gil version (that's still the default anyway!), but provide a no-gil version as a bonus. For example, Pytorch could provide thread-based data loaders if the no-gil version is in use.
> Is there a PEP or something written about it? I don't like this idea at first glance. Feels like another hack. Though I have to admit it sounds better than the annoying multiprocessing approach.
This seems very similar to Perl's ithreads model: https://perldoc.perl.org/perlthrtut#Shared-And-Unshared-Data
First, I don't see how the GIL would have much to do with free multithreading. The GIL should not have much observable logical impact on multithreaded _Python_ code. It should not make it more or less susceptible to race conditions. It's only practical impact should be slowness.
Second, your proposed "one GIL per thread" is pretty much the equivalent of the current state of multiprocessing. In that you fork your current interpreter state in an other thread with it's own GIL and start from there. This has been used for decades already, nothing new there. Sharing can be done through queues or shared memory.
It has nothing to do with multithreading or GIL, though, and happily works without threads and with GIL in place.
No, no the same thing. Sharing objects between processes is not easily achieved for various reasons (at least in Python). It would be easier to get it in multithreading with an arena based allocation model where objects live in a shared or non-shared area of memory.
Also it's not my idea. I am just advocating it as the better model for Python to advance to. It would also not take years to implement and the risks are minimized as there is full compatibility with existing code.
Well to me it is the same thing.
Two threads sharing state through a common heap, or two processes sharing state through a shared memory is pretty much indistinguishable, at least on Linux.
The question is not multithreading or multiprocessing anymore, the difference to me is more semantic than real.
The question is then just how these threads/processes communicate.
I would argue that shared mutable state is rarely a good idea, and an equivalent message based system is often preferable.
For the few use cases that remain where you would want shared mutable states, as mentioned in my original answer, Python has shared memory support, though with non-built-in types. Improving these shared types should be the only thing you advocate for.
Improving shared types would be a great addition. Problem is the reason for objects being unpicklable in the first place, is often a more underlying thing that can't be shared across processes, like open file handles.
It's just that (some) people really want multithreading. And with that Python had the rare chance to say, ok you'll get it, but we will make it safe by default.
Now they said ok you get it, even though it's a big risk, we hope it'll work. If not, we'll take it back.
In what world is "taking back" even an option?
The point is that isn't very practical, especially in python. In practice, any data passed between processes must be serialized and deserialized over a pipe or in shared memory. With a thread per interpreter, native python objecta could potentially be shared across threads/interpreters as long as there was adequate synchronization. Technically, you could also do that with shared memory with multiprocessing, but it would be harder, and you would need to specify the object is intended to be shared from the beginning.
> pretty much indistinguishable, at least on Linux.
Linux isn't the only OS people use python on.
> I would argue that shared mutable state is rarely a good idea, and an equivalent message based system is often preferable.
I agree, but even for message passing, having to serialize and deserialize the object across process boundaries hurts performance.
That still relies on IPC, and the associated kernel overhead to set, ctl and access the shared memory, IN ADDITION to the kernel overhead of having to do a context switch between processes.
Threads only rely on the context switch.
> I am just advocating it as the better model for Python to advance to
https://discuss.python.org/t/poll-feedback-to-the-sc-on-maki...
In multithreading there is no need for a context switch. Also memory is automatically shared by all threads. The arena model can be completely managed by Python.
I don't get why the SC has not chosen this. Well, I do get it, but it don't like what it implies.
This is like Ruby's Ractors and I haven't really seen that be super successful so far. The "Objects" that need to be shared are things like class definitions, etc.... there are a ton of subtle issues with objects that are being marked as sharable when they should really not be or vice versa.
You just described Ractors in Ruby, which didn’t turn out great. The setup cost for either freezing or copying memory to the target ractor to guarantee memory safety is often higher than the perf gains of the parallelism.
Not that it can’t work or won’t be improved. But there is a real world case study of what you’re recommending that we can reference without having to guess.
>offer a safe path to concurrent programming
Uh, doesn’t Python support this already? Python has “concurrency” from async and parallelism from multiprocessing. What it doesn’t support is thread-based parallelism. What you’re suggesting (if I understand it) is an implementation of parallelism that is barely different at all from the typical Python parallelism approach of using multiprocessing to achieve parallelism.
* Threads are also relatively fat. You'd probably think twice about spinning up 1000 of them.
* Futures came in with Java 8 and are lighter weight, but have certain implementation defects - can't cancel them for example. Although there are some instances in which regular threads aren't cancellable. Also, the API isn't terribly popular. I happen to like flatmapping my way around a codebase, but plenty of devs' eyes glaze over when they read that code.
* Fibers are released I think - at least in beta. More like JS's model I believe. They're even lighter weight and get rid of the flatmappy stuff again. I assume it's still up to the programmer to get the locking right.
From what I understand, this is how threading worked in perl. But that functionality is now "discouraged".
I do think that would have been a good way to do it. Especially with an emphasis on message passing.
Just like you can today spawn threading.Threads one by one in python, or just map a bunch of function calls over a threadpool using concurrent.futures thread pool.
Convince me otherwise