April 13th, 2017
(written by lawrence krubner, however indented passages are often quotes). You can contact lawrence at: firstname.lastname@example.org
I’m sad that Python is doing so well. It gets all the attention that should go to Clojure. They are both great for AI and NLP, but Python doesn’t have as many great ideas for concurrency. Or rather, the language doesn’t, but the eco-system makes up for that with a wealth of interesting ideas — and it is sad to see so much effort go to see making Python work, rather than just starting over with something that has a stronger fundamental basis.
Greenlets—aka cooperative threads, user-level threads, green threads, or fibers—are similar to threads, but the application has to schedule them manually. Unlike a process or a thread, your greenlet function just keeps running until it decides to yield control to someone else.
Why would you want to use greenlets? Because in some cases, your application can schedule things much more efficiently than the general-purpose scheduler built into your OS kernel. In particular, if you’re writing a server that’s listening on thousands of sockets, and your greenlets spend most of their time waiting on a socket read, your greenlet can tell the scheduler “Wake me up when I’ve got something to read” and then yield to the scheduler, and then do the read when it’s woken up. In some cases this can be an order of magnitude more scalable than letting the OS interrupt and awaken threads arbitrarily.
That can get a bit clunky to write, but third-party libraries like gevent and eventlet make it simple: you just call the recv method on a socket, and it automatically turns that into a “wake me up later, yield now, and recv once we’re woken up”. Then it looks exactly the same as the code you’d write using threads.
Another advantage of greenlets is that you know that your code will never be arbitrarily preempted. Every operation that doesn’t yield control is guaranteed to be atomic. This makes certain kinds of race conditions impossible. You still need to think through your synchronization, but often the result is simpler and more efficient.
The big disadvantage is that if you accidentally write some CPU-bound code in a greenlet, it will block the entire program, preventing any other greenlets from running at all instead, whereas with threads it will just slow down the other threads a bit. (Of course sometimes this is a good thing—it makes it easier to reproduce and recognize the problem…)
It’s worth noting that other concurrent designs like coroutines or promises can in many cases look just as simple as greenlets, except that the yields are explicit (e.g., with asyncio coroutines, marked by yield from expressions) instead of implicit (e.g., marked only by magic functions like socket.recv).