About structured concurrency notes--go A malicious statement in a language

Source: Internet
Author: User
# # Preface The code behind each concurrent API needs to be allowed to run concurrently, here are examples of using different APIs: ' Go MyFunc (); Golangpthread_create (&thread_id, NULL, &myfunc); /* C with POSIX threads */spawn (ModuleName, Myfuncname, [])% Erlangthreading.thread (Target=myfunc). Start () # Python with Threadsasyncio.create_task (MyFunc ()) # Python with Asyncio "has many variants in different symbols and terms, but the semantics are the same. The example above is to run ' myfunc ' in a concurrent way to use the program's idle resources, and immediately return to the parent process (or the main thread) to do something else. Another way is to use callbacks: ' ' Qobject::connect (&emitter, SIGNAL (Event ()),//C + + with Qt &receiver, SLOT (MyFunc ())) G_signal_ Connect (emitter, "event", MyFunc, NULL)/* C with GObject */document.getelementbyid ("myID"). onclick = MyFunc; Javascriptpromise.then (MyFunc, ErrorHandler)//Javascript with Promisesdeferred.addcallback (myfunc) # Python with Twistedfuture.add_done_callback (myfunc) # Python with Asyncio "And while the syntax is different, they all do the same thing: they're arranging the task (arrange), and then The MyFunc will run until an event has occurred. Registering "Event callback" succeeds, the above function returns immediately, and the caller can continue to do other things. (sometimes callbacks can be cleverly encapsulated as helpers, such as [Promise] (Https://developer.mozilla.org/en-US/docs/Web/JavaScript/RefereNce/global_objects/promise/all) [combinators] (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference /global_objects/promise/race), or [Twisted-style Protocols/transports] (https://twistedmatrix.com/documents/ current/core/howto/servers.html), but the core idea is the same) is there any other way? Any of the real-world concurrency APIs You use, you may find that they are all the same, such as Python's Asyncio. But my newly developed library [Trio] (https://trio.readthedocs.io/) is different, and it has not been used in any other way. Conversely, if we want to execute ' myfunc ' or other functions concurrently, we can write: ' ' Pythonasync with Trio.open_nursery () as Nursery:nursery.start_soon (MyFunc) Nursery.start_soon (Anotherfunc) "When people come across the ' nursery ' approach to construction, they want to understand the mystery of it, why is there an indentation?" Why do I need a ' nursery ' object to dispatch asynchronous tasks, people start to feel that this is against past usage habits, feel bored, this library is weird, far from the original grammar, these are understandable reactions, but please forgive me. In this article, I hope to persuade you that ' nurseries ' is not always weird and special, but rather a new control flow primitive, which is as important as looping or function calls. In addition, the other methods we see above-thread dispatch and callback registration-should be completely removed and replaced with ' nurseries '. It sounds hard to accept? A similar thing happened in history: the statement ' Goto ' was once thought to be the king in the flow of control, and is now still reduced to the [past] (https://xkcd.com/292/). Some languages still have a ' goto ' statement that is different or weaker than the original ' goto '. Most languages don't even have it. What happened? The time is so long that people forget the stories of the past, but the results are surprisingly similar. So we'll first remind ourselves of what Goto is, and then look at the concurrency API, which can teach meThings for them. # # Outline Directory-what exactly is ' goto '-exactly what is ' go '-' goto ' what happened 1. ' Goto ' abstract destroyer 2. A surprise, remove the ' goto ' statement to bring the new feature 3. ' Goto ' statement: no longer used-' go ' statement is considered harmful 1. Go statement: No longer used-nurseries: an alternative to the ' go ' statement of the widget 1. Nurseries supports function Abstraction 2. Nurseries supports dynamic task dispatch 3. There's a runaway 4. You can define a new type like nursery 5. No, in fact, nurseries always waits for the task inside to exit 6. Work on automatic cleanup of resources 7. Work 8 that automatically passes error messages. A surprising benefit: Remove the ' Go ' statement to open a new feature-practice nurseries-conclusion-acknowledgements-Footnotes # # ' goto ' What exactly is it that lets us review history: The early computers were programmed using assembly language, or other more primitive machine mechanisms. Some humble. As a result, in the 1950s, IBM John Backus and Remington Rand, Grace Hopper, began developing languages such as FORTRAN and flow-matic (known for their direct successor to COBOL). Flow-matic was very ambitious at the time. You can think of it as a Python Zengzenzu parent: The first language, first for human design, and then for computers. Here are some flow-matic codes that let you experience the look of it:! [] (https://raw.githubusercontent.com/studygolang/gctt-images/master/go-statement-considered-harmful/ Flow-matic-code-0.png) You notice that it's not like modern language, there's no ' if ' code snippet, ' Loop ' loop, or function function call, there's actually no block delimiter or indentation at all. This is just a simple list of statements (operators). Not because the program is too short to use more advanced control syntax, but because "block syntax" has not been invented!! [] (https://raw.githubusercontent.com/studygolang/gctt-images/master/go-statement-considered-harmful/ Compare-sequential-to-goto.png) Instead, Flow-matic has two ways of controlling the flow, compared to the usual order (sequential), as you would expect, to execute the statement from beginning to end, one sentence at a sentence, but if you execute a special sentence ' jump to ', It immediately jumps directly to other control statements. For example, the statement 13 jumps to Statement 2:! [] (https://raw.githubusercontent.com/studygolang/gctt-images/master/go-statement-considered-harmful/ Flow-matic-code-1.png) It is like the beginning of the concurrency primitive, there are some disagreements about how to call this "jump action" operation. This is ' jump to ', but this is called ' Goto ' (it's like "Go to"), so here we use this salutation. This small program uses the full ' goto ' Jump path:! [] (https://raw.githubusercontent.com/studygolang/gctt-images/master/go-statement-considered-harmful/ Flow-matic-code-2.png) If you feel this seems to be confusing, you are not alone! This jump-style program is ' flow-matic ' is very straightforward to inherit assembly language. This is very powerful and well suited to the way the computer hardware works, but it is very confusing to use it directly. The messy arrows have invented the word "spaghetti code". Obviously, we need something better. But ... Goto is the key to all of these issues? Why are some control structures good, some not? How do we choose a good? At this point, the problem is really not clear, if you do not understand the problem, it is difficult to solve the problem. # # ' Go ' exactly what it is but let's pause to review history-everyone knows that ' goto ' is bad. How does this relate to concurrency? Well, consider the go language's famous ' go ' statement, which is used to produce a new ' goroutine ' (Lightweight thread): ' golang//Golanggo myfunc (); "Can we draw its control flowchart? This differs from what we see above, because the control flow is actually split. We can draw like this:! [] (https://raw.githubusercontent.com/studygolang/gctt-images/master/go-statement-considered-harmful/gO-myfunc.png) The color here is divided into two paths. From the point of view of the main line (the Green Line), the control flow goes sequentially: It appears at the top and then immediately appears at the bottom. At the same time, from the point of view of the branch (lavender Line), the control flow goes to the top, then jumps to the body of the myfunc. Unlike regular function calls, this jump is unidirectional: When you run MyFunc, we switch to a completely new stack, and the runtime immediately forgets where we came from. But this applies not only to Golang. Here is the Process Control chart for all primitives that we have listed at the beginning of this article:-line libraries typically provides some type of handle object that you can later join-but this is a standalone operation that the language does not know about. The actual thread generation primitives have the control flow shown above. -The registration callback is semantically equivalent to starting a background thread, which blocks the background thread (a) until an event occurs, and then (b) runs the callback. (Although obviously the implementation is different) Therefore, in the case of high-level control flow, the registration callback is essentially a go statement. -' future ' and ' promise ' are the same: when you call a function and it returns a promise, it means that it plans to work in the background and then give you a handle object to join later (if you want). In terms of control flow semantics, this is like producing a thread. Then you register the callback on this promise, so you see the front point. The same exact pattern appears in many forms: the key similarity is that in all these cases, the control flow is separated, one-way jumps are made, and the other party is returned to the caller. Once you know what to look for, you will start to see it everywhere-it's a fun game! [1] It is disturbing that this type of control flow structure does not have a standard name. So, just like the ' goto statement ' becomes a generic term for all the different like goto structures, I'll use the ' go statement ' as a generic name for these terms. Why did you do this? One reason is that the Go language gives us a particularly pure form example. The other is ... Well, you may have guessed what I said. Take a look at these two graphs. Note any similarities:! [] (https://raw.githubusercontent.com/studygolang/gctt-images/master/go-statement-considered-harmful/ Compare-go-to-goto.png.png) Yes: The GO statement is a form of a goto statement. The writing and reasoning of concurrent programs is very difficult. The same is true for Goto-based programs. Is this possible for some of the same reasons? In modern languages, the problems caused by Goto are largely resolved. If we were to study him from theHow to fix "go to" it will teach us how to make more concurrent APIs available ", let's find out the answer. # # Goto What's going on? So why is goto causing so many problems? In the late the 1960s, Edsger W. Dijkstra wrote two well-known papers to help people understand this more clearly: [Goto statement is considered harmful] (https://scholar.google.com/scholar?cluster =15335993203437612903&hl=en&as_sdt=0,5) with [structured programming notes] (https://www.cs.utexas.edu/~EWD/ewd02xx/EWD249. PDF) # # Goto: Abstract destroyer in these papers, Dijkstra worried about how to write extraordinary software and make it right. I can't explain it in detail here. For example, you may have heard this sentence:> program test can be used to find the existence of a bug, but there is no way to prove that the bug does not exist Yes, this comes from [structured programming notes] (https://www.cs.utexas.edu/~EWD/ewd02xx/EWD249. PDF). But his main concern is abstraction. He wanted to write something too big and not write it all at once. To do this, you need to treat parts of your program like a black box-just like when you see a Python program: "' Pythonprint (" "Hello world!") "Then you don't need to know how the print is implemented (string formatting, buffering, cross-platform differences ...). ) in all the details. You just need to know that it will print the text you provide in some way, and then you can spend your energy thinking about whether you want this to happen at this point in your code. Dijkstra wants language to support this abstraction. So far, block syntax has been invented, and languages like Algol have accumulated 5 different types of control structures: they still have sequential flow and ' goto ':! [] (https://raw.githubusercontent.com/studygolang/gctt-images/master/go-statement-considered-harmful/ Compare-sequential-to-goto.png) and also obtained variants of if/else, loops, and function calls:! [] (Https://raw.githubusercontent.com/studygolang/gctt-images/masTer/go-statement-considered-harmful/if-loop-functioncall.png) You can use Goto to achieve these higher-level structures, and in the early days people would think of them as a handy shorthand. But Dijkstra points out that if you look at these charts, there is a big difference between Goto and other people. For all content except Goto, the flow control is at the top →[there is a problem]→ flow control is at the bottom. We can call it the "black box rule": If a control structure has this shape, then you can ignore the [stuff happen] section in the context where you don't care about the details that occur inside, and put the whole sequence of rules in the process. And better yet, any code that consists of these parts is the same. When I look at this code: "' Pythonprint" ("Hello world!") "I don't have to read ' print ' and all the definitions of its transitive dependencies, just want to know how the control process works. Maybe there's a loop inside ' print ' and there's a ' if/else ' inside the loop, and there's another function call inside ' if/else ' ... Or it could be something else. It doesn't matter: I know the control will flow into ' print ', the function will do its thing, and then the final control will return to the code I'm reading. It seems obvious, but if you have a language with a ' goto '--a language whose functionality and everything else is based on ' goto ', ' goto ' can jump anywhere--then these control structures are not black boxes at all! If you have a function and there is a loop inside the function and there is a ' if/else ' inside the loop, and there is a ' goto ' in ' if/else ' ... Then ' goto ' can send control to any place it wants. Maybe the control will suddenly return from another function you haven't called, you don't know! This breaks the abstraction: This means that every function call can be a disguised ' goto ' statement, and the only thing you need to know is to keep the entire source code of the system in mind at once. As long as ' Goto ' uses your language, you will stop local inference for traffic control. That's why ' goto ' causes the spaghetti code. Now that Dijkstra understood the problem, he was able to solve it. This is his revolutionary suggestion: we should stop ' if/loops/function call ' as a shorthand for ' goto ' and should use them as a basic primitive of our rights--and we should completely remove it from our language. ' Goto '. From 2018 onwards, this seems obvious. But when you try to take away their toys, have you ever seen the programmer react because they are not smart enough to use them safely? Yes, some things will never change. The proposal was incredibly controversial in 1969. [Donald Knuth] (Https://en.wikipedia.org/wiki/Donald_Knuth) for ' goto ' [Defense] (https://scholar.google.com/scholar?cluster= 17147143327681396418&hl=en&as_sdt=0,5). The people who used to be experts in coding were very dissatisfied, and they basically had to learn how to reprogram them to use newer, more constrained constructs to express their ideas. Of course, it needs to build a whole new set of languages. [] (https://raw.githubusercontent.com/studygolang/gctt-images/master/go-statement-considered-harmful/ Goto-change.png) Finally, the modern language is lesser than the original formula of Dijkstra. They will let you use ' break ', ' continue ' or ' return ' constructs to immediately jump out of multiple nested structures. But basically, they are all designed around the idea of Dijkstra, even though these push boundaries can only be constructed in a strictly limited way. In particular, ' function '-this is the basic tool for encapsulating control processes within the black box-is considered inviolable. You cannot jump out of another function from one function, and returning can take you out of the current function, but no further. Regardless of the control process, a function works internally, and other functions do not need to be concerned. This extends even to the Goto itself. You will find that several languages still have some of the languages they call Goto, such as C,c#,golang ... But they add a lot of restrictions. At the very least, they won't let you jump out of a function and dive into another function. Unless you work in assembly [2], the unrestricted goto is gone. Dijkstra won. # # # A surprise, remove the ' goto ' statement to bring new features once the Goto disappears, something interesting happens: The language designer can begin to add functionality that relies on controlling the structure of the process. For example, Python has some good resource cleanup syntax: with statements. You can write the following: "' python# PythoNwith Open ("My-file") as File_handle: ... "and ensure that the file will open during the ' ... ' Code, but will then close immediately. Most modern languages have some equivalent (RAII, use, trial resources, delay ...). )。 They all assume that the control flow is orderly and structured. What would you do if we used a goto statement to jump into a block of ' ... ' Between us? Is the file open? What if we jump out again instead of quitting normally? Will the file be closed? This feature simply does not work in any coherent way. Error handling has a similar problem: What should your code do when a problem occurs? Often the answer is to pass the stack in the stack to the caller of the code, and let them figure out how to handle it. Modern languages have specialized constructs to make this easier, such as exceptions or other forms of [automatic error propagation] (https://doc.rust-lang.org/std/result/index.html#the-question-mark-operator-). But your language can only provide this help if it has a stack and a reliable "caller" concept. Take a look at the control flow noodles in our flow-matic program, and imagine trying to throw an exception in the middle of it. Where does it even go? # # GOTO statement: No longer used so ' goto '--ignores the traditional type of function bounds--not just a common bad feature, which is difficult to use correctly. If that is the case, it may be preserved. But the reality is worse. Even if you don't use Goto, just use it as an option in your language, making it hard to work with everything. Whenever you start using a third-party library, you can't think of it as a black box-you have to read it carefully to find out which functions are regular functions and which are the special control structure flow that disguises. This is a serious obstacle to local inference. You lose powerful language features such as reliable resource cleanup and automatic error delivery. We should be better able to completely remove Goto to support control flow constructs that follow the "black box" rule. # # # Go statements are considered harmful so this is the history of Goto. Now, how much does this apply to ' go ' statements? So...... Basically, all of it! The analogy turned out to be shocking. The GO statement breaks the abstraction. Please remember how we say if our language allows jumps, then any function may be a disguised jump? In most concurrent frameworks, go statements can cause the exact same problem: whenever a function is called, it may or may not produce some background tasks. The feature seems to be back, but it still runs in the background? If you do not read all the source code, there is no way to know. When is it finished? Hardly. If you have a go statement, then the function is no longer relative to the black box to control traffic. InIn my first article on concurrent APIs, I called it a "violation of causality" and found that it was the source of many common real-world problems in programs that use Asyncio and Twisted, such as back-pressure problems, closing problems properly, and so on. The GO statement interrupts automatic resource cleanup. Let's once again the ' with ' Statement example: ' ' python# pythonwith open ("My-file") as File_handle: ... ' Before we say we are guaranteed (guaranteed), the file will be opened, The code runs and then finishes closing. But is there something that allows the code to generate a background task? Then our guarantee is lost: the operation looks like the internal operation of the ' with ' block is recycled after the ' with ' block, because the files are being used while they are still using it to close. Again, you cannot see it from the local inspection; You know, if this happens, you have to read the source code and look at the ' ... ' Code of the internal implementation function. If we want this code to work, we need to keep track of any background tasks in some way, and only manually schedule the files to close after completion. This is possible-unless we are using a library that does not provide any way to be notified when a task is completed, it is very common (for example, because it does not expose any task handles that can be joined). But even in the best of circumstances, unstructured control processes mean that language cannot help us. We are now back in the hands of implementing resource cleanup, just like in the bad times of the past. The GO statement breaks the error handling. As we discussed above, modern languages provide powerful tools, such as exceptions, to help us ensure that errors are detected and propagated to the correct location. However, these tools rely on a reliable concept that has the "caller of the current code". This concept is destroyed whenever you generate a task or register a callback. As a result, every mainstream concurrency framework I know is simply given up. If there is an error in the background task and you do not handle it manually, then the runtime is just ... It's not too important to put it on the floor and no longer take care of it. If you're lucky, it might print something on the console. (The only software I ever used to think "print something and keep going" is a good error-handling strategy, it's an old Fortran library, but here we are.) Even Rust-the language was voted "the most accurate person" in high school. If the background thread gets messy, Rust [discards the error and expects it to be better] (https://doc.rust-lang.org/std/thread/). Of course, you can handle errors correctly in these systems, carefully ensuring that each thread is joined, or by building your own error propagation mechanism, such as [Twis in JavaScript]Ted] (https://twistedmatrix.com/documents/current/core/howto/defer.html#visual-explanation) or [ Promise.catch in Errbacks] (https://hackernoon.com/promises-and-error-handling-4a11af37cb0e). But now you are writing a special, fragile re-implementation of the functionality that your language already has. You have lost something useful such as "backtracking" and "debugger". All you need is to forget to call ' Promise.catch ' once, and suddenly you don't even realize the serious error on the floor. Even if you solve all of these problems in some way, you'll still get two redundant systems to do the same thing. # # # GO statement: No longer used just like Goto is the obvious source code for the first practical high-level language, go is the obvious primitive of the first practical concurrency framework: It matches the way the underlying scheduler actually works, and it is powerful enough to implement any other concurrent streaming pattern. But again, like Goto, it breaks the control flow abstraction, so just use it as an option in your language to make everything more difficult to work with. The good news is that these problems can be solved, and Dijkstra shows us how to do it:-Find alternatives to statements with similar functionality, but follow the "black box rules"-Build this new construct into our concurrency framework as a primitive and do not contain any form of GO statements. That's what Trio did. # # # Nurseries: An alternative to the ' go ' statement the following is the core idea: each time our control splits into multiple concurrent paths, we make sure that they are summed up again. For example, if we want to do three things at the same time, our control flow should look like this:! [] (https://raw.githubusercontent.com/studygolang/gctt-images/master/go-statement-considered-harmful/ Control-flow.png) Note that only one arrow appears at the top and one appears at the bottom, so it follows the Dijkstra black box rule. Now, how can we turn this sketch into a concrete language structure? There are some existing constructs that can satisfy this constraint, but (a) My proposal is slightly different from what I know and has an advantage over them (especially if you want to make it a standalone primitive), and (b) concurrency is large and complex, trying to separate all history and trade-offs will cause the argument to fail completely, So I'm going to put it off.to another separate article. Here, I am only interested in explaining my solution. But please note that I'm not saying I like it, inventing concurrency or something, I'm on the shoulders of giants, drawing inspiration from many sources. Anyway, here's what we're going to do: first, we declare that the parent task cannot start any subtasks unless it first creates a place for the subtasks: nurseries. It achieves this by opening a nurseries block; In trio, we use Python's ' async with ' syntax to do this:! [] (https://raw.githubusercontent.com/studygolang/gctt-images/master/go-statement-considered-harmful/ Python-async-with.png) Opening a nursery block automatically creates an object that represents the nurseries, and the nursery syntax assigns the object to a variable named nursery. We can then use the Start_soon method of the nursery object to start the concurrency task: In this case, one task calls the function MyFunc, and the other invokes the function Anotherfunc. Conceptually, these tasks are carried out within the nurseries area. In fact, it is often convenient to treat code written in a nursery block as an initial task that starts automatically when a block is created. [] (https://raw.githubusercontent.com/studygolang/gctt-images/master/go-statement-considered-harmful/ Python-nursery.png) Most importantly, the nurseries block does not exit until all the tasks within the nurseries block have exited-if the parent task reaches the end of the chunk before all subtasks are complete, it pauses and waits for them. Nurseries automatically expands to accommodate child tasks. Here's the control process: you can see how it matches the basic pattern we showed at the beginning of this section:! [] (https://raw.githubusercontent.com/studygolang/gctt-images/master/go-statement-considered-harmful/ Python-nursery-control-flow.png) This design has many consequences, not all of which are obvious. Let's take a look at some of them. # # NuThe basic problem with the Rseries support function abstract Go statement is that when you call a function, you don't know if it will produce some background tasks and continue to run when you're done. With nurseries, you don't have to worry about this: any function can open nurseries and run multiple concurrent tasks, but the function cannot be returned until it is finished. So when a function returns, you know it's really done. # # # Nurseries supports dynamic task dispatch this is a simpler prototype and it also satisfies the Process Control chart above us. It needs a list of thunk and runs them simultaneously: "' pythonrun_concurrently ([MyFunc, Anotherfunc]) ' But the problem is that you have to know the complete list of tasks you're going to run, and that's not always the case. For example, a server program typically has an accept loop that receives incoming connections and starts a new task to handle each of them. This is the smallest accepted loop in trio: ' Pythonasync with Trio.open_nursery () as nursery:while true:incoming_connection = await server_socket . Accept () Nursery.start_soon (Connection_handler, incoming_connection) "has nurseries, which is insignificant, but uses to implement it Run_ Concurrently will be more awkward. If you want, you can easily implement run_concurrently on top of the nurseries-but this is not necessary because the nursery symbol is as readable as run_concurrently can handle in simple cases. # # # There's a runaway nurseries object that also gives us an escape exit. If you do need to write a function that creates a background task, will the background task go beyond the function itself? This is also easy: a nurseries object by function. Asynchronous code calls Nursery.start_soon directly within the Open_nursery () block-as long as the nursery block remains open [4], any person who obtains a reference to the nurseries object can get the ability to distribute the task into that Nurseries. You can pass it as a function parameter and send it through the queue. In practice, this means that you can write a "rule violation" function, but within the following limitations:-Because the nurseries object must be explicitly passed, you canLocal reasoning is still possible by looking at its call site immediately to identify which features violate the normal control flow. -Any task generated by the function is still constrained by the life cycle of the incoming nurseries. -The calling code can only access the nurseries object through its own permission. So this is still very different from the traditional model, where any code can generate background tasks for an unlimited life cycle. There is a very useful place to prove that nurseries has considerable expressive power to make statements, but this article has been long enough, so I will stay in the next article. # # # You can define a new type of standard nurseries semantics like nursery provides a solid foundation, but sometimes you want something different. You might envy Erlang, and you want to define a class like nurseries that handles exceptions by restarting subtasks. It is entirely possible for your users to look like a normal nurseries: "Pythonasync with My_supervisor_library.open_supervisor () as Nursery_alike: Nursery_alike.start_soon (...) If you have a function that takes nursery as a parameter, you can pass one of them to it to control the error handling policy for the task it generates. Pretty pretty. But here's a subtlety that pushes Trio toward different conventions, not asyncio or other libraries: This means that start_soon must take a function, not a co-object or a future. (You can call a function multiple times, but there is no way to restart a co-object or future.) I think it's a better deal anyway due to a variety of reasons (especially because Trio doesn't even have future! ), but it is still worth mentioning. # # # No, actually, nurseries always waits for the task inside to exit. It is also worth discussing how task cancellation and task affiliation interact, as there are some subtleties-if handled incorrectly-the nurseries invariant is broken. In trio, the code may receive a cancellation request at any time. After the request is canceled, the next time the code executes the Checkpoint action (details) (https://trio.readthedocs.io/en/latest/reference-core.html#checkpoints), a cancellation exception is thrown. This means that there is a gap between the request cancellation and the actual time-the task may take a while before the checkpoint is executed, and then the exception mustYou must expand the stack, run the cleanup handler, and so on. When it happens, nurseries always waits for full cleanup. We will never terminate the task, nor will it have the opportunity to run the cleanup handler, and we will never allow the task to be unsupervised outside of nurseries even in the process of nurseries being canceled. # # # Automatic cleanup of resources because nurseries according to the black box rules, it allows the ' with ' block to work again. Prohibit this behavior: the code block at the end of a background task is still executing. # # # Automatic delivery of error messages as described above, in most concurrent systems, the unhandled errors in the background task are simply discarded, too late to do anything else. In Trio, because each task is within nurseries, each nurseries is part of the parent task, and the parent task waits for the task within the nurseries ... We do have some things that can be done with an unhandled error. If the background task terminates with an exception, we can re-throw it in the parent task. The intuition here is that a nurseries is like a "concurrent call" primitive: we can consider the above example as calling MyFunc and Anotherfunc at the same time, so our call stack has become a tree. The exception propagates the call tree to the root as if they were propagating a normal call stack. Here's one nuance: when we re-throw an exception in the parent task, it starts propagating in the parent task. Typically, this means that the parent task exits the nurseries chunk. However, as we have said, the parent task cannot leave the nurseries block while the task is still running. So what do we do? The answer is that when an unhandled exception occurs on a subtask, Trio immediately cancels all other tasks in the same nurseries, and then waits for them to complete before re-throwing the exception. This exception directly causes the stack to release, and if we want to free one branch point in our stack tree, we need to expand the other branches and cancel them. This does mean that if you want to implement nurseries in your language, you might need to do some sort of integration between the nurseries code and your cancellation system. If you use a language such as C # or Golang, this can be tricky, usually through manual object passing and conventions to manage cancellations, or (worse) without a generic cancellation mechanism. # # # A surprising benefit: Remove the ' go ' statement to turn on a new feature elimination Goto enables former language designers to make more assumptions about the structure of the program, enabling new features such as blocks and exceptions; Removing the GO statement has a similar effect. For example:-Trio's logoff system (cancellation System) is easier to use and more reliable than a competitor because it can assume that the task is nested in a regular tree structure; See [End timeout and man-made cancellation] (https://vorpus.org/blog/timeouts-and-cancellation-for-humans/). -Trio is the only Python concurrency library where control-c works the way Python developers expect ([details] (https://vorpus.org/blog/ control-c-handling-in-python-and-trio/)). Nurseries provides a reliable mechanism to handle exceptions. # # Practice Nurseries This is the theory. How does it work in practice? This is a practical question: you should try it out and find out the answer! But, seriously, we have experienced problems before we understand. At this point, I am very confident that the foundation is sound, but perhaps we will realize that we need to make some adjustments, such as how early structured programming advocates ultimately respond to the elimination of ' break ' and ' continue '. If you're an experienced concurrent programmer and they're just learning trio, you should expect it to get used to. You will have to learn new ways to do things-just like programmers in the the 1970s, learning how to write code without ' goto ' is a challenge. But of course, that's the point. As Knuth wrote (knuth,1974,p.275):> Perhaps the worst mistake any one can make in relation to the underlying statement is to assume that "structured programming" is implemented by writing programs, because we always have, and then eliminate it. Most of the go should not be there! What we really need is such a way that we rarely even conceive of our plans to go about stating, because what they really need is hardly there. The language in which we express our thoughts has a strong influence on our thinking process. As a result, Dykstra requires more new language features-encouraging structures of clear thinking-to avoid the temptation to do so. So far, this is my experience of using nurseries: it encourages clear thinking. It makes the design more robust, easier to use, and better. These limitations actually make it easier to solve problems, because you spend less time trying out unnecessary complex problems. In a very real sense, the use of Trio has taught me to become a better programmer. For example, consider the Happy eyeballs algorithm ([RFC 8305] (https://tools.ietf.org/html/rfc8305)), a simple concurrency algorithm that acceleratesEstablish a TCP connection. Conceptually, the algorithm is not complex-you tried multiple connection attempts and staggered to avoid overloading the network. But if you look at [Twisted's best implementation] (Https://github.com/twisted/twisted/compare/trunk...glyph:statemachine-hostnameendpoint), It has almost 600 lines of Python and still [has at least one logical error] (https://twistedmatrix.com/trac/ticket/9345). 15 times times more than the same type of project as Trio. More importantly, using trio, I can write it in a few minutes instead of months, and I get the right logic when I try it for the first time. I never could have done that in any other framework, even if I had more experience. For more detailed information, you can watch [my speech last month at Pyninsula] (Https://www.youtube.com/watch?v=i-R704I8ySE). Is this just an example? Time will prove everything, it is full of hope. # # # Conclusion Popular concurrency Primitives-go statements, thread spawning functions, callbacks, Futures, promises, and so on, are all variants of Goto, both theoretically and practically. Even modern domesticated ' goto ', but the old ' goto ' can jump out of a function boundary. Even if we do not use them directly, these primitives are dangerous because they undermine our ability to infer control flows and form complex systems from abstract modular parts, interfering with useful language functions such as automatic resource cleanup and error propagation. So, like Goto, they have no place in modern high-level languages. Nurseries provides a secure and convenient alternative to preserving the full functionality of your language, enabling powerful new features (such as Trio's cancellation scope and control of C processing), and can significantly improve readability, productivity and correctness. Unfortunately, to fully enjoy these benefits, we need to completely remove the old primitives, which may require building a new concurrency framework from scratch-just as you would eliminate the goto required to design a new language. But the performance is impressive compared to flow-matic, and most of us are happy that we have upgraded to a better product. I don't think we'll regret it. Switching to Nurseries,trio also proves that this is a practical design for a common concurrency framework. # # Thanks Thank you very much Graydon Hoare,quenTin Pradet and Hynek Schlawack comments on the draft of this article. Any remaining errors are, of course, my fault. # # References-flow-matic sample code from [This brochure (PDF)] (http://archive.computerhistory.org/resources/text/Remington_Rand/ univac.flowmatic.1957.102646140.pdf) stored in [Computer history Museum] (http://www.computerhistory.org/collections/ catalog/102646140)-[Wolves in Action] (https://www.flickr.com/photos/iam_photo/478178221) by I:am. Photography/martin Pannier, licensed under [Cc-by-sa 2.0] (https://creativecommons.org/licenses/by-nc-sa/2.0/)-[ French Bulldog Pet Dog] (https://pixabay.com/en/french-bulldog-pet-dog-funny-2427629/) by Daniel Borker, posted in [CC0 public Domain Dedication] (https://creativecommons.org/publicdomain/zero/1.0/)

via:https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/

Author: Nathaniel J. Smith Translator: Lightfish-zhang proofreading: polaris1119 Magichan

This article by GCTT original compilation, go language Chinese network honor launches

This article was originally translated by GCTT and the Go Language Chinese network. Also want to join the ranks of translators, for open source to do some of their own contribution? Welcome to join Gctt!
Translation work and translations are published only for the purpose of learning and communication, translation work in accordance with the provisions of the CC-BY-NC-SA agreement, if our work has violated your interests, please contact us promptly.
Welcome to the CC-BY-NC-SA agreement, please mark and keep the original/translation link and author/translator information in the text.
The article only represents the author's knowledge and views, if there are different points of view, please line up downstairs to spit groove

268 Reads
Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.