Structured Concurrency
This is a modal window.
The media could not be loaded, either because the server or network failed or because the format is not supported.
Formal Metadata
Title |
| |
Subtitle |
| |
Title of Series | ||
Number of Parts | 561 | |
Author | ||
License | CC Attribution 2.0 Belgium: You are free to use, adapt and copy, distribute and transmit the work or content in adapted or unchanged form for any legal purpose as long as the work is attributed to the author in the manner specified by the author or licensor. | |
Identifiers | 10.5446/44558 (DOI) | |
Publisher | ||
Release Date | ||
Language |
Content Metadata
Subject Area | ||
Genre | ||
Abstract |
|
00:00
Concurrency (computer science)Control flowConstructor (object-oriented programming)Greatest elementFunction (mathematics)CodeImplementationProcess (computing)Functional (mathematics)CodeEncapsulation (object-oriented programming)Connected spaceDataflowCASE <Informatik>Field (computer science)System callSequenceCodebuchClassical physicsLibrary (computing)Formal languageMessage passingMereologyNeuroinformatikNetwork topologySoftware developerRight angleProgrammschleifeTrailGoodness of fitBlock (periodic table)Greatest elementHydraulic jumpBuffer overflowPoint (geometry)Context awarenessArrow of time1 (number)Computer programmingBlogCone penetration testDesign by contractMultiplication signCategory of beingControl flowSoftwareBlack boxAuthorizationGame controllerSource codeThread (computing)Computer animation
07:41
Function (mathematics)Fiber bundleThread (computing)Propagation of uncertaintyException handlingServer (computing)Connected spaceLibrary (computing)Point (geometry)System callDifferent (Kate Ryan album)Block (periodic table)CoroutineCore dumpExtension (kinesiology)Order (biology)VideoconferencingFunctional (mathematics)Group actionComputer programmingNumberRight angleComputer configurationFiber bundleState of matterLoop (music)Reading (process)Formal languageGame controllerCASE <Informatik>Message passingBitThread (computing)Control flowMereologyVirtual machineCodeImplementationReal numberRankingDeterminantWeb pageError messageRevision controlUniqueness quantificationProcess (computing)Object (grammar)Social classInheritance (object-oriented programming)2 (number)GodFinite-state machineContext awarenessDesign by contractStress (mechanics)System administratorSequenceHydraulic jumpTrailRadical (chemistry)Normal (geometry)Data structureClassical physicsSimilarity (geometry)SynchronizationBit rateComputer animation
15:15
Euler anglesComputer animation
Transcript: English(auto-generated)
00:05
OK, so maybe this should have been the part of a retrocomputing track, because it goes as far back as 1968, when Dijkstra sent a memo talking about GoTo is bad and why we should avoid
00:21
it. And the memo was then redistributed by Niklas Berf under the name GoTo Consider Harmful. And that's the original source of the Consider Harmful phrase. OK, so what's wrong with the GoTo in the first place?
00:42
Do we have to get some context? Like, you can put it in different ways, but this particular way of putting it is I've shamelessly stole from Nathan L. Smith, who wrote a nice blog post about it. And it goes like this. This is a program in Thaumatic, which
01:01
is the predecessor of COBOL, and it's unstructured language. So as you see, there are no loops, nothing. It's not even indented. And you see a lot of jumps there. So you look at a program, and you have no idea what's going on. And then you start drawing arrows, like how the control flow looks like.
01:22
And it looks like this. So you look at it, and you have no idea what's going on. So what's wrong with that? You have different constructs, like if, or for, or whatever,
01:41
and GoTo, and some of them are good, some of them are bad. And how should we know which ones are good and which ones are bad? And one way of thinking about it is let's say the construct is good if the control flow starts in the top, and then it ends up in the bottom.
02:02
And if you look at all those constructs, like all of them have this nice property that just goes from the top to the bottom. If you do an if block, it can do the if part or the else part, but eventually it will arrive in the bottom. And same for a for, or function call, or just sequential sequence of commands.
02:22
But not so for GoTo. For GoTo, it just jumps somewhere, and it never turns. It never reaches the bottom of the code block. So we like to believe that the functions are black boxes. We call a function, and the function eventually returns.
02:44
Nice, and then easy to reason about. So we have a main function. It calls function foo, which then calls function bar, but eventually it will return from bar and return to foo. And it's also nice for encapsulation,
03:00
because main doesn't have to know that foo calls bar. Because you change the implementation, it calls something else, you don't care. It still returns back. But try to use a GoTo somewhere, and suddenly that's no longer the case. Just somewhere in the nested function,
03:21
it jumps somewhere else, never to return. And now here we have developer Bob, who's very cautious. He doesn't use any GoTos, but he happens to use a library written by Alice, who's very cautious as well. She doesn't use any GoTos, but she uses a library written by Colin, who's careless,
03:41
and who uses GoTo. And suddenly Bob is hurt by that, because now inside the dependency tree, just flow, control flow, just somewhere else, and Bob's function call foo to foo never returns. Like all the assumptions he had about his code are broken.
04:01
So this is kind of, it's not only like bad design, it's bad design plus plus, because it's transitive. Like it takes only one dependency in your whole dependency tree, and you are screwed. So, what I was talking about was the old kind of GoTo
04:21
that probably not many people here really remember, there were like big bad GoTos which could jump around the code base, like from a function to a different function, and you don't see those anymore. Right, so, but it's actually not that long ago.
04:42
When I started programming in 1984, I've got Sinclair's X Spectrum, and it had like built-in basic, and it actually looks very much like the flowmatic code I've shown before. There were GoTos all around the place, and they just jumped around. So, you don't see this anymore, but what we have now is this kind of thing.
05:04
Domesticated GoTo which can jump only inside a function. Right, so that's the GoTo in C. Like you can jump inside a function, but there's no way to jump out. So, it's still bad, but it's at least not transitive. Right, so, the call in, the developer
05:23
of nested nested dependency can really break your workflow. Okay, so, now that I've explained that the whole GoTo thing from 50 years ago,
05:41
what does it have to do with modern programming thing? And I'm coming from the networking background. I'm an author of ZeroMQ, and I've made several networking libraries. And the problem that occurs all the time is how to handle asynchronous calls, basically. Right, so, this is what you have on wire.
06:01
There is a byte saying that the following field is going to be 13 bytes long, and then you have the actual data. And how are you going to process that? So, the old school way of doing that was like classic Unix way where you launch a process for each connection, and the process will be nice and sequential, and you just read this, read that, and that was it.
06:21
But then, people realized that launching a process per connection is probably not a good thing because it's slow and takes a lot of resources. So, we have to do better. And so, the threads were born. And the interesting thing about threads is you can't really find out who invented them. Probably the person was too ashamed to.
06:42
Okay, so, what we have to deal with nowadays is this kind of thing. We have this async function, which looks like a function, but it's not really a function because you would expect it to do whatever
07:02
is inside the block and then return as a normal function, but it doesn't. It returns immediately, and then at some later point, the async part is done. So, if you just look at this naively as if this was a normal function, you would expect it to print one, and then to print three, and then to print five, and four, and two.
07:22
But actually, what you have is one, two, and this part is executed three, four, and then five. So, it's weird. And yeah, it gets even worse in JavaScript, where basically those functions are anonymous and nested inside the code.
07:41
And this looks like a normal structure code, but it executes in very different ways. So, the layout of the program has basically nothing to do with the order of execution. So, what you see here is one, three, five, four, two. What's printed out is one, two, three, four, five,
08:03
I guess, well. Okay, the other option to do it in is classic state machines, where you remember like what state you are in in a variable. And well, this is at least explicit about what it's doing.
08:21
So, you go, you are in this state, so you switch to a different state, you will launch a sync call, and at some later point, the function's called again, with the reading done message, and the other part of the same machine is executed. But look at this code, it's terrible. And this is on one slide,
08:40
but if you look at the real implementation, it's like pages and pages of this kind of stuff, nobody can really grasp it at first sight. So, then enter Golang. So, then the nice thing about Golang is that it introduced coroutines as a first class citizen,
09:03
and you are back to the old Unix style programming, where you basically launch a process for each connection, and it's nice and sequential. Except it's not a process, it's a coroutine, which means it's cheap. Like, the stack is at the beginning, like two kilobytes, and the context rate is pretty quick,
09:20
like 20 nanoseconds or something. So, okay, so it seems that we are done. But what's wrong with the go-to? With go construct, watching of coroutine. You see, it kind of looks like the old go, because it's, on one way, it just continues,
09:41
but in the background, somehow the control flow just jumps away to some different function. It's very similar to go, in a way. So, and you have the same problem as we had before. So, once again, Bob launches a coroutine from Alice's library, which launches a call from Colin's library, and Colin does go,
10:02
and launches a coroutine, which is running in background, and the call returns to Bob's code, and Bob thinks, okay, the bar thing is done, but there's actually a coroutine running in the background, and Bob has no idea. So, it's transitive again.
10:22
Like, if any of your dependencies launches a coroutine in the background, you have the old problem from 50 years ago, with this wild, big, bad go-to. So, how easy is it to fix? So, one would say, okay, let's make coroutine scoped, right?
10:42
So, we have a scope, we launch a coroutine, and when we get to the end of the scope, the coroutine will be canceled, and that's it, except that that doesn't really work, because, look here, it is the classical code. So, we have a server, which accepts connections, and each connection is handled by a handler, which is a separate coroutine,
11:02
but if it was canceled at the end of the block, well, it would be actually canceled immediately, and nothing would happen. So, that's not good enough. So, what we really need is two different kind of scopes. One is the normal scope, and one is the better scope, which cancels all the coroutines
11:21
that were launched inside it, right? So, this is in C. This is code using a library that I wrote called libdail, and basically, you see that the two scopes here. There is a thing called bundle. It's a bundle of coroutines. You initialize it there in the while.
11:42
You launch any number of coroutines inside it, and once you want to shut down your server or something, you jump out of the loop, here at the break, and you close the bundle, and that cancels all the coroutines. So, it's kind of, you see, it's indented, nicely structured. So, there are several languages that implemented this,
12:03
like this is in Python, a library called trial, which does the pretty similar thing, except it's nicer. So, you open a thing they call nursery, which is the same as bundle in the previous example, and yeah, you basically do the same thing. You launch the coroutines,
12:20
and once you get out of the block, all the coroutines are canceled. So, is that it? Is that easy? So, there are still some open problems here. We are not yet there. Like, for example, there is a difference between how libdila handles the termination and how trail handles the termination.
12:43
What trail does is, you start your nursery, you launch three coroutines, and when you get to the end of the block, it waits. It waits till all of them are finished, and when they are finished, it just continues. And if there is an error in any of them, you just write an exception, and when the exception reaches this point,
13:02
it cancels all the other coroutines, and then it continues. libdila does it differently. So, there, you open a bundle, you launch some threads, and at some point, you say, close bundle, and at that point, the coroutines are closed. So, the difference really is that,
13:24
in this case, you have kind of like main thread, which is in control of all other threads. In the other case, all of them are created equal, and that there's no like the controller and the control. So, which way is better? It's pretty hard to tell, because in the trial version,
13:43
you have this nice error propagation, where you throw an error, and the error gets to the caller coroutine, and it bubbles up until it reaches main. But the problem there is, if that happens, then it kind of looks like that one of the coroutines can cancel its siblings,
14:05
and even its parents, and maybe just very unstructured way of doing stuff. So, that's not nice about it. So, it would be nice to know whether we can combine the two approaches, that we would have nice error handling, and kind of nice encapsulation,
14:22
where a coroutine cannot cancel its parents. Okay, so some other problems that are still unsolved, or partially solved. How would we handle timeouts? So, one way is to use something like a Golang's context,
14:40
where you have an object on which you can call, like cancel the timeout function, and it will cancel after it, or you can pass it manually to each function. Who knows what's better? Great spirits, that's a hard problem. So, for example, you want to shut down your server, and you say, okay, I am giving you like five seconds
15:02
to finish whatever stuff you are doing, to close the connection decently, and then shut down. So, we don't have a good story for that. Okay, I'm sorry to interrupt. We're out of time, unfortunately. The timer started a bit late. Thanks. So, thank you very much for the talk. Thank you.