3 ms·
Coroutines are brittle and buggy
- klodolph 14y agoCoroutines are not the same thing as green threads, which the author seems to half acknowledge but then really screws up the terminology for the rest of the article. Green threads are things that can be implemented using coroutines, or they can be implemented without coroutines. 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.
- DanWaterworth 14y agoThis article has succeeded in convincing me that both ways are equally bad.
- redbad 14y agoWell, his example code demonstrating the "bug" is definitely broken. But not because of any flaw in coroutines, rather in his understanding of them. 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.)
- StavrosK 14y agoExactly. If you're relying on coroutines not yielding for your synchronization, you're going to get bitten hard. Use per-coroutine data structures and lock when accessing shared structures. 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.
- stevedekorte 14y agoHis only complaint seems to be race conditions, but callbacks have these too. 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.