Coroutines are brittle and buggy
williamedwardscoder.tumblr.com
williamedwardscoder.tumblr.com
Python generators are coroutines, a specific kind of coroutine. And you don't see anyone calling them brittle or buggy.
The author's real complaint here is that the traditional shared-memory-plus-locks model of concurrent programming is hard, and that's absolutely true. I don't see how using a cooperative scheduler makes writing correct programs any easier or harder.
session = sessions.get_session(cookie);
if(!session)
session = sessions.create_session(user_id);
Concurrent access to a global sessions object is obviously unsafe, even in a coroutine environment where "You know [the above methods] don’t yield to the scheduler." I'm not sure that anyone advocating for coroutines over async APIs is arguing that coroutines somehow magically absolve the programmer from considering race conditions. Only that coroutines, used idiomatically, remove classes of bugs _like this one_. And that snippet is not idiomatic.One "coroutine way" of handling concurrent access to shared data is by piping requests to it through a synchronization point. For example, in pseudo-Go, it may look like
// public, synchronous API method
func (s *Sessions) GetSession(cookie string) Session {
// return s.dataStructure[cookie] // bad, obviously
responseChannel := make(chan Session)
s.requests <- getSessionRequest{cookie, responseChannel}
return <-responseChannel
}
func (s *Sessions) loop() {
for {
select {
. . .
case req := <-s.requests:
req.responseChannel <- s.dataStructure[req.cookie]
. . .
}
}
}
(Another way is explicit locking, of course.)I tend to think about coroutines as threads, never relying on their determinism, as that's not something you can count on, and it's not advertised as one of their features.
In fact, coroutine based i/o use callbacks underneath just as while and for loops use gotos (jump instructions) underneath.
The difference with both is that the abstraction allows your code to be more understandable and easily changed.
For example, try making randomly selected locations in your code async using callbacks - painfully difficult. Now try it with coroutines - easy.
There is a reason why we use stacks.