6 ms·
Unpopular opinion: This is a missed opportunity. What? Python could have been the one language with a sane multithreading model. Now it risks becoming a secon
by miraculixx 3y ago
Unpopular opinion: This is a missed opportunity.
What?
Python could have been the one language with a sane multithreading model. Now it risks becoming a second version of Java. I fear this will make it a less attractive programming language, not least because it might lose its beginner friendlyness. For example, without the GIL a lot more care must be put into designing your programs. This can be true even though your own code is single threaded, for example when you use a library that is multithreaded and has callbacks to your code
Why?
Free threading as introduced by PEP 703 is well known to be errorprone, hard to get right and generally advised against, unless you know exactly what you are doing. In other words free threading is for expert (as in very experienced) use only. And Python already has an expert mode - called Cython oer Numba to name just two.
Personally I can see no good will come from bringing free threading to the masses. Yes it addresses a common critique (by many) and need (by very few), but it addresses it in a very risky way (for the vast majority of Python users).
A better alternative
IMHO the far better and still my preferred approach would have been to favor a per-thread GIL with an explicit mode to share particular objects. This would benefit everyone without the risks. It would be consistenly beginner friendly, and above all, offer a safe path to concurrent programming without impacting the whole ecosystem. Heck we could even call it the "Pythonic Threading Model", and it would be seen as a differentiator.
- ajkjk 3y agoWhy convince you otherwise? You're the one with the weird opinion, you should be convincing us.
- banthar 3y agoYou can pin your JVM process to a single core and will effectively get Python multithreading model.
- superjared 3y ago> Personally I can see no good will come from bringing free threading to the masses 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.
- miraculixx 3y agoHow do you suggest we could teach proper threading (what is that?) to the masses?
- nyanpasu64 3y agoRust is a brilliant lesson in using traditional threading safely. It uses & for thread-shared types and constrains &mut to a single thread, which naturally causes people to keep single-threaded data on an object only accessible from a single thread, and make multithreaded data either immutable, mutex-protected, or atomic. Alternatively, message-passing isn't traditional threading, but Erlang/Go-style languages are another way to approach concurrency or parallelism.
- miraculixx 3y agoGreat examples. Unfortunately that's not what the Python SC has decided to go for.
- Galanwe 3y agoI don't quite see how Rust approach to multithreading is that amazing. 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.
- nyanpasu64 3y agoIn my experience with multi-threaded programming, C++ code with "careless sharing issues" is often filled with multiple threads accessing the same object and relying on convention to avoid calling the wrong thread's methods, pervasive data races and unsynchronized variable access, mistaken use of mutexes on only one side of shared memory, and logical race conditions that require adding mutexes (risking deadlock) or rewriting code to address. Whereas Rust code tends to not have these issues to begin with (outside of the implementation of synchronization primitives), store reader and writer methods on separate handle objects, use Arc to manage cross-thread shared memory, and similar which makes the code either correct or tractable to learn and make correct. 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 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.
- n2d4 3y agoThe GIL does very little to protect unexperienced users. It's still really easy to run into race conditions, for example, if your thread gets scheduled out in any multi-instruction operation (this is more common than you think [1]). In general, Python code still has to be thread-safe; you get the risks without the benefits. 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`). [1] https://verdagon.dev/blog/python-data-races https://verdagon.dev/blog/python-data-races
- paulddraper 3y ago> this is more common than you think Like, for example, count += 1 --- The GIL primarily protects C code, not Python code.
- asvitkine 3y agoI don't think the blog you cite backs up your claim of "it's still really easy to run into race conditions". 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.
- mlyle 3y ago> I don't think the blog you cite backs up your claim of "it's still really easy to run into race conditions". > 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.
- paulddraper 3y agoPython is already Java but worse. All of the multithreading bugs without any of the multithreading performance.
- dpedu 3y agoThat's not quite true. For example, most operations on lists and dicts are thread-safe in the current version of python. You can't say that about languages with true multithreading.
- wtetzner 3y agoOf course Java has thread safe collections in the standard library. ConcurrentHashMap etc. are popular in multithreaded contexts.
- dpedu 3y agoThe difference is that it's a language feature in python whereas the java equivalents had to be written with locking or other approaches for handling concurrent access.
- wtetzner 3y agoIt's not a language feature in Python though. The GIL doesn't automatically make code thread safe.
- lmm 3y agoIt does, but you have to opt into them.
- veave 3y ago>Free threading as introduced by PEP 703 is well known to be errorprone, hard to get right and generally advised against, unless you know exactly what you are doing. You should hear about async...
- miraculixx 3y agoIndeed. You should hear my opinions on async (in Python). ;)
- Phil_Latio 3y ago> sane multithreading 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...
- miraculixx 3y ago> It's simply not "sane multithreading" not being able to run Python (byte)code concurrentltly 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.
- Phil_Latio 3y ago> I am advocating a GIL per-thread model with an explicit feature for sharing selected objects 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.
- Galanwe 3y agoI don't quite get your "unpopular opinion". 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.
- miraculixx 3y agoYes, used for decades - and for good reason and benefit. 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.
- Galanwe 3y ago> No, no the same thing. 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.
- Too 3y agoRight now, you often get stuck on using multiprocessing because you need to share one object that is unpickleable and can't be sent over queues. 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.
- xerxes901 3y ago> a per-thread GIL with an explicit mode to share particular objects 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.
- schneems 3y ago> IMHO the far better and still my preferred approach would have been to favor a per-thread GIL with an explicit mode to share particular objects. 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.
- miraculixx 3y agoThanks for this. I'll read up on that. Any good links pointers to start?
- opportune 3y agoWouldn’t that require running an interpreter in each thread? How on earth could that be a “sane multi threading model”? >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.
- dingi 3y agoI’m genuinely curious, What's wrong with Java's model?
- mrkeen 3y ago* It's hard to program correctly using the thread model. And saying that locks are the solution to thread safety issues in Java, is like saying that malloc/free is the solution to memory safety issues in C. * 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.
- thayne 3y ago> my preferred approach would have been to favor a per-thread GIL with an explicit mode to share particular objects. 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.
- kzrdude 3y agoI want to agree with you. My hope is that PEP 703 can lead to better implementations of even your safe / structured concurrency model. It will after all still be possible to use, it just won't unfortunately be the only option. 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.