I'm assuming your stack looks like mine: gevent has monkey patched everything.
1. fork'ing just doesn't work properly; something about the event loop in the child hangs.
2. You can't easily spawn an honest to god hardware thread anymore. (Since the typical call is monkey patched!) Yes, you can get the original threading module by getting it out of gevents hands, but…
3. …it doesn't compose well. Libraries, in order for gevent to play nice, need special "if gevent then …" sections.
No need for forking or threads, that's the point of async. Yeah there's limitations when CPU bound, but that's python. Fire up a few processes and load balance.
#3 hasn't been an issue for us, monkey patching works well. We've switched to pure python versions of a few libraries. When not available use a pool of instances.
The problem is with some specific features like select.epoll. gevent doesn't support that and will break such code. This includes socketio libraries - we use third party tools like pusher.com and all of them break with python 3.
E.g. https://github.com/benoitc/gunicorn/issues/1494
Gevent explicitly says :
"Given that epoll is more sophisticated than select or poll (e.g., support for edge triggered and level triggered events), I don't know if we can emulate that close enough with the capabilities that libev exposes (we'd need to pass the stdlib tests when monkey-patched), and I don't know what the performance characteristics would be (and if the subtle differences would break apps) "
I don't see a path forward for gevent with these issues in place.
Of course you need threads even with async. When you code blocks for CPU reasons, like handling a heavy HTTP request with lots of calculations, you don't want to stop serving other requests.
It depends. If the calculation can be split into nice chunks you can throw in some gevent.sleep(0) to prevent them from hogging the process. We use python for handling web requests, anything heavy tends to be handed off to another process. It's rare for us but yeah, sometimes you need threads.
First, forking is "firing up a few processes". Yes, you can do that prior to launching your process, and then let gevent just have the process, but you have to be able to do that, and it just doesn't make sense in all contexts. This is what I meant by gevent fights composability.
> or threads
There's a limit to what a single CPU can do, and eventually multiple hardware threads can buy you actual tangible results.
> Yeah there's limitations when CPU bound, but that's python.
My point being that it's very difficult to get to CPU bound when your greenlet library prevents you from taking advantage of threads or processes. "but that's python" implies to me that you think Python is hamstrung to a single thread, and that's not the case: Python is quite capable of not only hardware threads, but performing computation on hardware threads in parallel, simply because not all of my code is in Python. Sometimes, a component knows it needs an honest-to-god HW thread, and the monkey patching fights that.
HW threads additionally lets you run multiple-long running computations; even if they're mutually bound to a single CPU, preemptive multitasking at the OS level (and Python's interpreter occasionally releasing the GIL) will ensure they all get time. In a cooperative greenlet, if you do not yield, you starve the other threads. A library making a long running computation again needs intimate knowledge that at the higher level, greenlets are in use: this breaks layers and mixes responsibilities (greenlets) in where it doesn't belong (some library calculating a thing). Greenlets do not compose.
> We've switched to pure python versions of a few libraries.
And you pretty much have to; if the library called out to C, it would block. But this fights good engineering practices: you can't just implement your library once in the language of choice, and then bind it into another language.
> When not available use a pool of instances.
This depends on what the library does; a "pool" does not necessarily save you. For example, if the library blocks, you need a threadpool (a real one) to process inputs to it.
Libraries exposing an event loop, perhaps with some basic primitives, have a hope of communicating across things like language barriers.
Excellent points, thanks for the thoughtful response. I suppose it depends on the use case and certainly there are limits to gevent, but for our use case--serving and routing web requests, they rarely come up and are easily worked around. In some cases these pain points can be avoided by only patching the libraries you need rather than monkey.patch_all(), other times we can just spawn another process to do the work. I can appreciate that there are workloads where asyncio is better suited than gevent, but gevent is just so easy.
We communicate across languages quite a bit using sockets. A given system may have 20 processes that communicate with each other and the outside world. Most but not all of the python processes use gevent. Composing software with sockets scales well and avoids compatibility issues. It's rare for our python processes to be cpu (or core) bound, but it's easy to be IO bound and that's what gevent solves.
1. fork'ing just doesn't work properly; something about the event loop in the child hangs.
2. You can't easily spawn an honest to god hardware thread anymore. (Since the typical call is monkey patched!) Yes, you can get the original threading module by getting it out of gevents hands, but…
3. …it doesn't compose well. Libraries, in order for gevent to play nice, need special "if gevent then …" sections.