This is the 21st day of my participation in the August More Text Challenge
goroutine
Note: Preemptive scheduling has been implemented in go version 1.14 and later, while non-preemptive scheduling was implemented in previous versions. If there is any incorrect place below, welcome to correct
Antecedent – auxiliary understanding
Concurrent programming is one of the more distinctive aspects of the Go language, which has a native support for concurrent programming. This is relatively rare in general-purpose languages. Here is an example of how it supports concurrent programming
package main import ( "fmt" "time" ) func main() { for i:=0; i < 10; I ++ {go func(I int) {// the anonymous function used here. You can also wrap a function of your own, For {fmt.printf ("Hello From goroutine %d\n", I)}}(I)} time.sleep (time.millisecond)}Copy the code
Can be found that there is a very important key words go, this go keyword in front of the function, and the go after the function call will be concurrent execution, a total of 10 in the concurrent execution and the printed word, and bring your own number (in order to can see there are lots of people in printing the words, So a numeric number was added). A total of 1 millisecond printing time, during this 1 millisecond time, these 10 are concurrent printing (note: the above is the anonymous function, you can also write a function, before the function name of the call before the function name, is the same. If you don’t know about the go anonymous function, click here.)
As you can see, the variable I is passed as a parameter to the anonymous function. Although we can use it directly without passing it, this method is not safe, and why is not safe will be explained later. So, I’m going to pass in I as an argument
If the above anonymous function is not preceded by the keyword go, it is equivalent to calling the anonymous function 10 times, and the result is that it will print the following sentence repeatedly
Hello From goroutine 0 Hello From goroutine 0 Hello From goroutine 0 .....
Copy the code
Because there’s an infinite loop inside the anonymous function, and it never returns, so the first loop I =0 is executed in an infinite loop
With the go keyword, instead of calling the function, it executes it concurrently. So, in fact, the main program is still running down, and then concurrent open a function, function inside the continuous printing of a sentence. This is the equivalent of opening a thread (for now), but it’s not actually a thread, it’s a coroutine. From what I can tell, it’s about the same. It’s like having 10 Goroutines running this anonymous function all the time
Note that I’ve also added this line of code to the bottom of main
time.Sleep(time.Millisecond)
Copy the code
It just tells main to sleep for a millisecond and then execute down. If I didn’t do that, if I did main, I would see that I couldn’t print anything, and I would just exit, right
The reason for the exit is that main and our 10goroutine are executed concurrently, and the goroution on the side can’t print anything, so main loop 0~10 ends, and main returns. In Go, if Main exits, all goroutines are killed. So, the Goroutines were killed before they could print anything
To see the open Goroutine print something, the mian function should not exit for a while, so let it sleep for a millisecond, and then execute the above program and see the following result:
Hello From goroutine 9 Hello From goroutine 9 Hello From goroutine 5 Hello From goroutine 2 Hello From goroutine 2 Hello From goroutine 2 Hello From goroutine 2 Hello From goroutine 2 Hello From goroutine 2 Hello From goroutine 2 Hello From goroutine 0 ......Copy the code
You can see the different Goroutines printing things all the time
There are actually only 10 Goroutines on it (for loop 10 times), which is not unusual. You can also set it to 1000 and have it open 1000 Goroutines to execute functions inside. (You can see that it prints normally, but not every Goroutine has a chance to be executed within this millisecond.)
Hello From goroutine 644
Hello From goroutine 644
Hello From goroutine 644
Hello From goroutine 89
Hello From goroutine 501
Hello From goroutine 501
Hello From goroutine 501
Hello From goroutine 501
Hello From goroutine 814
Hello From goroutine 688
Hello From goroutine 688
......
Copy the code
What does this 10 have to do with 1000? If you’re familiar with an operating system, 10 threads is fine, 100 threads is not a big deal, but it’s close enough. In a typical operating system, each person opens dozens or hundreds of threads, which is already a big deal, but if you have a thousand, a thousand people have to do something concurrently, which can’t be done simply with threads. In other languages, this is done by asynchronous IO, with 1000 people doing it concurrently
But in the go language, we don’t care, open 10 can be, open 1000 can be, anyway, in front of the keyword go, it can be executed concurrently. Now what is a coroutine in theory
The definition of goroutine
- Any function can be sent to the scheduler simply by adding go
- There is no need to define an asynchronous function (this is for Python, the python coroutine is an asynchronous function, and I’ll talk about support for coroutines in various languages in the next article).
- The scheduler switches carefully at the right point. It is non-preemptive (1.13 and earlier), but there is a scheduler that can switch, and the point at which it switches is not completely controlled. This is one of the differences between a Goroutine and a thread
- Use -race to detect data access conflicts (demonstrated later)
Coroutines Coroutine
A goroutine is actually a coroutine, or it’s similar to a coroutine. Call Coroutine coroutines
What is a coroutine? What does it have to do with threads?
- Lightweight “threads”
It’s similar to what threads look like. It’s used to perform tasks concurrently, but coroutines are lightweight. As we saw earlier, we can open 1000 Goroutines, but threads are heavy
- Non-preemptive multitasking in which the coroutine voluntarily relinquishes control
Nonpreemptive means that the coroutine voluntarily cedes control. Thread as we all know, thread at any time may be the operating system to switch, so the thread is preemptive multitasking, it has no control over, a statement execution to a half, even if it may be to stop in the middle, and the operating system and to perform other tasks, then the operating system behind will come back to continue
Coroutines are non-preemptive, and it’s decided internally when you want to relinquish control and when you don’t want to. It is because of non-preemption that coroutines are lightweight. In preemption, you have to deal with the worst case, and when I grab it, they’re in the middle of it, and they need to save more stuff, more context. Non-preemption only needs to handle a few of these switching points, so it consumes less resources
- Multitasking at the compiler/interpreter/virtual machine level
It is not an operating system level multitasking. In Go language, on the one hand, it can be seen as multi-tasking at the compiler level. The compiler will interpret the Go function as a coroutine, and there is a scheduler in Go to schedule the coroutine during execution. The operating system already has a scheduler, and Go has its own scheduler to schedule its own lightweight coroutines
- Multiple coroutines can run on one or more threads
This is determined by the scheduler. It can be said to run in one thread, or it can be said to run in multiple threads
Non-preemptive multitasking
Let’s take a look at non-preemptive multitasking. From the print above, it is no different from preemption. Because a Goroutine doesn’t print all the time, it gets snatched up by another Goroutine. This is because Printf is an IO operation, and there is a switch in the IO operation, because the IO operation always has a waiting process
Now I’m going to try to keep it from switching, so I’m going to change it to something like this
func main() {
var a [10]int
for i:=0; i < 10; i++ {
go func(i int) {
for {
a[i]++
}
}(i)
}
time.Sleep(time.Millisecond)
fmt.Println(a)
}
Copy the code
We define an array of length 10. In the function, we continuously add each goroutine to the corresponding position. After sleep, we print the array
You should be smart enough to know what the results will be. As mentioned above, coroutines are non-preemptive multitasking. Printf involves IO operations, so it switches between coroutines. But a[I]++ is just a normal instruction, executed without switching between coroutines and thus hijacked by a coroutine. This coroutine is always in this coroutine unless it voluntarily relinquish control. So the result of the above execution is a crash, and it will remain stuck in the loop. (Note: this is true in 1.13 and earlier versions, but it can print normally in 1.4 and later versions because 1.14 and later implement preemptive scheduling.)
Preemptive scheduling for Go will be covered separately later. For now, stick with 1.13 and earlier versions
Main is itself a Goroutine, and it executes a sleep, but no one relinquishes control, so it never sleeps out. If you want to hand over control after executing a[I]++, add the statement below a[I]++
Runtime.gosched ()// Hand over controlCopy the code
But in general, it’s rarely used, and it’s just for demonstration purposes. There are usually other opportunities to switch, which will be discussed later
Pay attention to
As I mentioned above, the I that is needed in the anonymous function can be passed, and it will take the outside I itself, so let’s try it instead of passing it directly to the anonymous function, what happens
func main() { var a [10]int for i:=0; i < 10; I ++ {go func() {for {a[I]++ runtime.gosched ()}}()} time.sleep (time.millisecond) mft.println (a)} runtime error: index out of range [10] with length 10Copy the code
Executing this program results in an out-of-bounds subscript. To check data access conflicts, run the “go run-race xxx.go” command
You can see that there is a WARNING: DATA RACE, 7 The (random) goroutine reads DATA from an address, and main writes DATA to that address. As you might have guessed, that address is actually the address of variable I, and you can print the address of variable I to prove it. So, basically, the goroutine main writes data into variable I, and then the other goroutine reads data into variable I
If we don’t pass that I into an anonymous function, which is the concept of functional programming shared in the previous article (see functional programming for GO here), it refers directly to the outside I, which is the same I as the inside I. Therefore, the outside I keeps adding up, and when the outside I jumps out, I becomes 10. When I =10, the closure references a[10], so it will fail
So we need each goroutine to pin I down, so we pass I in
This article may not be particularly in-depth, but I’ll comb through a more in-depth article on the GO language scheduler
reference
Senior Google engineers explain Go in depth
The Go Programming Language — Alan A. A. Donovan
Go Language Learning Notes — Rain Marks