LemonHX

LemonHX

CEO of Limit-LAB 喜欢鼓捣底层的代码,意图改变世界
twitter
tg_channel

Go language wins twice and leads by a wide margin.

Goroutines are a mechanism in the Go language used to implement cooperative multitasking, making Go a simple and efficient concurrent programming language.

The key is that this thing is much easier to understand than teaching a beginner what Async is.

Don't bother arguing with me that Go is difficult to learn. Just yesterday, I taught someone with no Go experience how to use Go in just two hours.

In Go, each goroutine has its own independent stack, which can store temporary values and return addresses, among other information. In fact, the Go compiler allocates a very small stack for each goroutine at the beginning and expands it as needed.

Now let me explain why go and async are essentially doing the same thing.

Let's take an example of how goroutines are implemented in Go using the TinyGo instance. It is based on the async/await model used in languages like C#, JavaScript, and now also in C++. In fact, TinyGo automatically inserts the async/await keywords because TinyGo is single-threaded.

Let's use the following code snippet as an example:

func main() {
	go background()
	time.Sleep(2 * time.Second)
	println("some other operation")
	n := compute()
	println("done:", n)
}

func background() {
	for {
		println("background operation...")
		time.Sleep(time.Second)
	}
}

func compute() int {
	time.Sleep(time.Second)
	println("blocking operation completed")
	return 42
}

If we manually add async/await and convert it to a syntax that JavaScript developers may be more familiar with, it would look like this:

async func main() {
	go background()
	await time.Sleep(2 * time.Second)
	println("some other operation")
	n := await compute()
	println("done:", n)
}

async func background() {
	for {
		println("background operation...")
		await time.Sleep(time.Second)
	}
}

async func compute() int {
	await time.Sleep(time.Second)
	println("blocking operation completed")
	return 42
}

I believe most users won't have any problems understanding this. But don't worry, we don't really need to learn async/await.

Introducing Delimited Continuation#

If you don't understand, please refer to the article Delimited Continuation

Delimited Continuation is a programming model used to control the execution flow of a program and save and restore execution states when needed. It provides a flexible and powerful mechanism for control flow transfer, relieving developers of mental burden.

In traditional control flow, the execution order of functions is linear, with each function having an entry and an exit. When one function calls another function, control is transferred to the called function, and when the called function completes, control is returned to the calling function. This linear control flow is not suitable for some complex programming needs, such as callback hell in asynchronous programming and coroutine implementation.

Delimited Continuation allows the current execution state to be saved at any point in a function and transfer control to another code fragment. This code fragment is called a continuation, which can be a closure, a function, or any other executable code block. Delimited Continuation provides the ability to carry execution states to different code fragments, enabling non-local control flow transfer.

Related to Delimited Continuation is the Async Await model, which is a programming model used to simplify asynchronous operations and widely used in modern asynchronous programming. Async Await allows developers to write asynchronous code in a synchronous manner without explicitly handling callback functions. It waits for asynchronous operations to complete by suspending and resuming the execution of functions, and continues with subsequent code after the asynchronous operations are completed.

There is a connection and sharing of concepts between Delimited Continuation and Async Await. Async Await is actually an asynchronous programming model implemented based on Delimited Continuation. When using Await to suspend an asynchronous function, the current execution state is actually saved to a delimited continuation and resumed after the asynchronous operation is completed.

The benefits of automatically handling delimited continuation transformation include:

  1. Simplifying asynchronous programming: Using delimited continuation can simplify the asynchronous programming model, making it more intuitive and easier to understand when writing asynchronous code. Developers can use asynchronous features as if they were writing synchronous code, without dealing with complex callback functions and nesting.

  2. Reducing errors and improving maintainability: Automatically handling delimited continuation transformation can reduce errors in coding and improve code maintainability. Since this transformation is automatically done by the compiler, common errors that occur when manually handling control flow issues are less likely to happen.

  3. Simplifying complex control flow: Delimited continuation provides a more flexible and fine-grained control flow transfer, making it easier to handle complex control flow patterns. Developers can use delimited continuation to implement higher-level control structures such as coroutines and state machines.

Did you get it?#

Go's biggest advantage is that the entire language is linear, without complicated control flow (assuming you don't use recover). The only concurrency mechanism in the language is go, so is it easy to convert it to delimited continuation format?

Assuming my compiler has a pass that can convert Go code to delimited continuation format...

This pass is very simple because with only the go function and <-, converting the code to CPS style is relatively easy.

Implementing automatic coloring for Async Await is more difficult...

Implementing automatic coloring for Async Await requires considering the complexity of the compiler and runtime environment. This kind of automation requires the compiler to statically analyze and transform the code, marking asynchronous operations as suspension points and resuming execution at the appropriate time. The compiler needs to perform syntax and semantic analysis and generate the corresponding intermediate representation or target code.

Implementing automatic coloring for Async Await is technically complex. This pass needs to handle complex control flow, code generation, and optimization issues. It requires a deep understanding of the compiler and the underlying language's runtime mechanism.

In terms of runtime resource consumption...

At runtime, the automatic coloring for Async Await model typically requires maintaining additional state, such as the state of asynchronous tasks and the context of suspension points. This additional state may require extra memory and processing overhead, increasing the complexity of the runtime. Additionally, executing asynchronous operations may require scheduling and managing the execution of coroutines or threads, involving context switching and scheduler overhead.

We would like to thank C 艹 for providing the LLVM coroutine, which efficiently executes coroutines by transforming the code, generating state machines, and optimizing coroutine switch points. It marks the coroutine switch points and controls the coroutine switch using the generated state machine. This avoids unnecessary context switches, reducing performance overhead and memory usage.

Conclusion#

Go took a shortcut, iterated on its technology, and it seems like Go has won again.

Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.