Live data from Hacker News

An easy way to concurrency and parallelism with Python stdlib

bitecode.dev

61–70 of 70 posts

Re: An easy way to concurrency and parallelism with Python stdlib

#61
post #17

I recently have been doing--what should be--straightforward subprocess work in Python, and the experience is infuriatingly bad. There are so many options for launching subprocesses and communicating with them, and each one has different caveats and undocumented limitations, especially around edge cases like processes crashing, timing out, killing them, if they are stuck in native code outside of the VM, etc. For exam…

To be clear, Popen is very different from all the other options. That's for running other programs. Process is low-level and is almost never what you want. Pool is "mid-level", and usually isn't what you want. ProcessPoolExecutor is usually what you want, it is the "one obvious way to do it". That's not at all clear from the docs though. The one obvious way to do it, in general, is: subprocess.run for running externa…

You're saying ProcessPoolExecutor is the "one obvious way to do it" but mention how the docs don't make this clear... That makes it not obvious. And since Python has built-in async/await keywords for asyncio now, shouldn't that be the one obvious correct way of doing concurrency?

Imagining I'm a newbie to Python concurrency, I Googled "concurrency in Python" and picked the first result from the official docs. https://docs.python.org/3/library/concurrency.html It's a list of everything except asyncio, and the first item on the list is the low-level `threading` :S At least that page mentions ThreadPoolExecutor, queue, and asyncio as alternatives, but I'm still lost on what is the correct way.

Re: An easy way to concurrency and parallelism with Python stdlib

#62

Earlier quoted context omitted.

Title: "An easy way to concurrency and parallelism with Python" Content: basically how to use ThreadPoolExecutor Comment: Concurrency and parallelism aren't easy in Python. How is this off-topic?

It's mostly about communicating with subprocess and Popen, which has little to do with this article, other than being Python modules you can use with concurrent futures. It's also long-winded and beside the point. Shouldn't be the top comment.

Subprocessing is the only way to do full parallelism in Python. The title includes parallelism, and the article says how threading can achieve it specifically if your CPU-bound portion is inside C modules (which release the GIL), but it's relevant to mention how you do parallelism in the general case.

Re: An easy way to concurrency and parallelism with Python stdlib

#63

Earlier quoted context omitted.

It's mostly about communicating with subprocess and Popen, which has little to do with this article, other than being Python modules you can use with concurrent futures. It's also long-winded and beside the point. Shouldn't be the top comment.

Subprocessing is the only way to do full parallelism in Python. The title includes parallelism, and the article says how threading can achieve it specifically if your CPU-bound portion is inside C modules (which release the GIL), but it's relevant to mention how you do parallelism in the general case.

Problems communicating with a crashy subprocess are not what I came to the thread for. Certainly not had issues like that myself.

If you wrote the subprocess, add quality and some communication hooks. If you didn't, get a better one or kill -9 it regularly.

Re: An easy way to concurrency and parallelism with Python stdlib

#64

Earlier quoted context omitted.

JS doesn't have green threads, just a single threaded event loop and context switching via promises or async/await. Green threads implies parallelism implemented in user space (ala. GoLang goroutines or JVM virtual threads).. JS is not parallel only concurrent.

Greenthreading implies concurrency, not parallelism, implemented in userspace rather than OS. Two Java/whatever greenthreads atop a single OS thread cannot run in parallel. It's switching contexts (as managed in userspace) during I/O waits, just like the JS event loop. You call Goroutines threenthreading, and some Golang users would disagree, but it is too. Some environments support "M:N" greenthreading, mapping mult…

> Two Java/whatever greenthreads atop a single OS thread cannot run in parallel

Well.. yes. Actually that makes sense!

I guess I just never thought of them as green threads in JS because you don't interact with them as an object like you can in other languages.

Re: An easy way to concurrency and parallelism with Python stdlib

#65

Earlier quoted context omitted.

Greenthreading implies concurrency, not parallelism, implemented in userspace rather than OS. Two Java/whatever greenthreads atop a single OS thread cannot run in parallel. It's switching contexts (as managed in userspace) during I/O waits, just like the JS event loop. You call Goroutines threenthreading, and some Golang users would disagree, but it is too. Some environments support "M:N" greenthreading, mapping mult…

> Two Java/whatever greenthreads atop a single OS thread cannot run in parallel Well.. yes. Actually that makes sense! I guess I just never thought of them as green threads in JS because you don't interact with them as an object like you can in other languages.

"Greenthreading" is a weird term because it often refers to a very old Java implementation that was removed in 2000. And the Wikipedia article on the term is plain wrong in some ways.

Re: An easy way to concurrency and parallelism with Python stdlib

#66

> For those, Python actually comes with pretty decent tools: the pool executors. Delusion level: max. You have to be in a very, very bad place when this marginal improvement over absolute horror-show that bare Process offers seemed "pretty decent". Python doesn't have good tools for parallelism / concurrency. It doesn't have average tools. It doesn't have even bad tools. It has the worst. Though, unfortunately, it's…

> It doesn't have even bad tools. It has the worst. > It's not the only language in this category Soo....not the worst? :) Or tied for it? What do you find difficult/wrong with pool executors? Also, you reference "Process", but FYI the article talks about multiple threads, not multiple processes.

> Soo....not the worst? :)

Yeah... I know, it's hard to imagine that there could be more than one worst. But, as I have to practice these things with my 4 year old, I become more patient with adults who don't get the concept too.

Imagine you are in a class and the teacher gives everyone a pencil and a sheet of paper. Now, you want to find out who has the shortest pencil. All students compare their pencils and turns out that there are several pencils that are of the same exact length, and those are the shortest ones at the same time. So, more than one student has the shortest pencil.

But it doesn't end there. Not all sets which define a "greater than" relationship are totally ordered. In such sets it's possible to have multiple different smallest elements. Trivially, in a set that's not ordered, every element is the smallest.

> What do you find difficult/wrong with pool executors?

Difficult? -- I don't know.

Wrong? -- Well, it's pretty worthless... does it make it wrong? -- That's up to you to decide.

The idea of threads is bad for many reasons: one in particular is of how exceptions in threads are handled. But this isn't unique to Python. Python just made a bad decision to use threads in the language that's supposed to be "safe". Python thread implementation craps its pants when dealing with many aspects of threads. For example, thread-local variables. Since threads are objects in Python, you'd expect local variables to be properties on those objects... well the mechanism to use them is just idiotic and nothing like you would expect. When it comes to interacting with "native" code from Python, you'd expect some interaction with Python's scheduler so that the native code can portion its own execution, allow Python to interrupt it etc. but there's nothing of the kind.

Even though we haven't even gotten to the pools yet, pools, obviously, don't address any of the thread-related problems. If anything, they only amplify them. Specifically, the pool from concurrent package is worse than its relative from multiprocessing package because it uses "futures". The whole idea of "futures" is somehow broken in Python because of the neverending bugs related to deadlocking. It's been repeatedly "fixed", but every now and then deadlocks still happen. Here's the latest one I know of: https://bugs.python.org/issue46464 .

I've gone once down the rabbit hole of trying to make a native module work with Python threads... there's no good way to do it, but pools, be it from concurrent.futures or from multiprocessing are both very bad for many reasons. I was hoping to be able to give users an ability to control how parallel my native code is through the tools exposed by Python already, but that turned out to be such a disaster that I've given up on the idea. Python's thread wrappers are worthless for the native code that wants to actually execute concurrently -- they are only designed to execute Python code, non-concurrently. Like I already mentioned, Python has no infrastructure to communicate to the native code its scheduling decisions, no thread-safety in memory allocation, the code is overall poorly written (as in missing const, other imprecise typing, memory-inefficient data-structures)... there are no benefits to using that vs rolling your own. Only struggle with bad decisions.

Re: An easy way to concurrency and parallelism with Python stdlib

#67

> For those, Python actually comes with pretty decent tools: the pool executors. Delusion level: max. You have to be in a very, very bad place when this marginal improvement over absolute horror-show that bare Process offers seemed "pretty decent". Python doesn't have good tools for parallelism / concurrency. It doesn't have average tools. It doesn't have even bad tools. It has the worst. Though, unfortunately, it's…

If it's the worst, how is it not the only language in this category? How do you rank C, Perl, JavaScript, PHP, ... parallelism compared to execution pool + futures here? The absolute MAX WORST?

It's possible to have more than one worst. In a totally-ordered collection this happens if you have two or more equal elements, which happen to be worse or equal to any other element. In a partially-ordered collection, there could be groups of elements that are not comparable between each other, and so you will potentially have multiple distinct worst elements.

Trivially, in a collection that has no "worse than" relation you can define one that doesn't compare them at all, and declares them all "incomparable" -- which, again, would make them all worst.

Bonus question: can you imagine a collection where there is no worst element?

> How do you rank C, Perl, JavaScript, PHP

Well, none of these languages have their own parallelism / concurrency aspect. (Except Perl 5 maybe? I'm not really familiar with the language). They all rely on the system running them to do the parallelism.

So... all of these will go roughly into the same bin as Python?

Some languages have libraries that would allow them to do better (eg. you have PThreads in C), but that's not the function of the language.

Re: An easy way to concurrency and parallelism with Python stdlib

#68

Earlier quoted context omitted.

If it's the worst, how is it not the only language in this category? How do you rank C, Perl, JavaScript, PHP, ... parallelism compared to execution pool + futures here? The absolute MAX WORST?

While I'm painting with broad brush, I'll guess that the parent divides languages into two categories "Go" and "the worst".

Not really.

There are languages which put at least some effort into parallelism / concurrency (and Go would be one of those along with Java, Erlang, Ada, Clojure, even C++ to some extend).

Then there are languages which outsource everything to the system: eg. Lua, Ruby. They have a way in the language to make a system call, and so if the system can create multiple processes or multiple threads, they can use that.

There are languages that have no way to do even that. For example JavaScript, XSLT or SQL. Surprisingly, a lot of these handle concurrency very well in their runtimes due to automatic parallelization performed by the runtime (not the language).

Python is the language that has neither design nor discernible goals. It has some parallelism in the language, but it's lacking important components which are then either outsourced to the system, or aren't there at all. Because of the randomness of the "design decisions" Python cannot also be reliably automatically parallelized, nor do the developers have reliable tools for building parallel applications, especially not in a modular way because different modules may not agree on the way to go about parallelization.

Python has always been a language where you need to be really knowledgeable about things outside of Python and about Python's own implementation details to get ahead. If all you knew was Python, you'd do very poorly. This is in contrast to languages like Java, which put a great deal of attention towards making sure that even the dumbest programmer will not screw up too much.

Now the people who know how to use Python well are gone, and the language is gradually transforming into Java. But it still has a very long road ahead before it can do enough hand-holding for the losers. Parallelism is one of those things where the goals are very far and so far, mostly, unattainable.

Re: An easy way to concurrency and parallelism with Python stdlib

#69

Earlier quoted context omitted.

To be clear, Popen is very different from all the other options. That's for running other programs. Process is low-level and is almost never what you want. Pool is "mid-level", and usually isn't what you want. ProcessPoolExecutor is usually what you want, it is the "one obvious way to do it". That's not at all clear from the docs though. The one obvious way to do it, in general, is: subprocess.run for running externa…

You're saying ProcessPoolExecutor is the "one obvious way to do it" but mention how the docs don't make this clear... That makes it not obvious. And since Python has built-in async/await keywords for asyncio now, shouldn't that be the one obvious correct way of doing concurrency? Imagining I'm a newbie to Python concurrency, I Googled "concurrency in Python" and picked the first result from the official docs. https:/…

I would say that criticizing the documentation is distinct from criticizing the language itself. The Python standard library has had documentation problems for a while now, but realistically so does pretty much every other programming language. If you want to learn how to do things, you need a book.

If you're still interested in the topic, async/await is intended to be single threaded by default, but has some support for pushing jobs off to threads or processes, using a concurrent.futures Executor internally. Normally if I want process parallelism however, I don't bother with async/await and I go for the more explicit solution.

Again, I think there is a very clear sense of the one obvious way to do it in the minds of many python programmers, but it might not be expressed well in the official documentation. This would be a great opportunity to write a book, for example.

Re: An easy way to concurrency and parallelism with Python stdlib

#70

Earlier quoted context omitted.

You're saying ProcessPoolExecutor is the "one obvious way to do it" but mention how the docs don't make this clear... That makes it not obvious. And since Python has built-in async/await keywords for asyncio now, shouldn't that be the one obvious correct way of doing concurrency? Imagining I'm a newbie to Python concurrency, I Googled "concurrency in Python" and picked the first result from the official docs. https:/…

I would say that criticizing the documentation is distinct from criticizing the language itself. The Python standard library has had documentation problems for a while now, but realistically so does pretty much every other programming language. If you want to learn how to do things, you need a book. If you're still interested in the topic, async/await is intended to be single threaded by default, but has some support…

The language itself has the issue of there being many separate ways to do equivalent things here. And async/await wasn't in the language until recently, so people got used to the old ways.

I didn't need a book to deal with Javascript concurrency, for example. JS had its event loop as far back as I can remember, but users are getting concurrency via that without really understanding it anyway. It got promises a while back. Async/await is just syntactical sugar on top of promises. There's hardly any other way to do things. NodeJS has extensions for subprocesses and worker threads, but you don't end up there unless you're looking for a way to do parallelism, and even then you can get by with small Stackoverflow examples.

Post reply on HN