This is a creation in Article, where the information may have evolved or changed.
Catalogue [−]
- Go statement
- Deep GO statement
- What is Goroutine?
- Dispatch of Goroutine
This chapter describes the go statement, goroutine scheduling.
Go statement
The GO statement is used to generate a new goroutine and execute a function, which is very simple to use before a function call or method call is preceded by the Go keyword.
Functions can be existing functions, anonymous functions, methods, and so on, note that anonymous methods (method literals) do not forget to call.
123456789101112131415 |
func int int {return i * i} ... go foo()gofunc() {} ()go os. Open ("./test.txt") buf: = bytes. Newbufferstring ("Hello World")go buf. ReadString(0) |
Deep GO statement
Look at the following section of code, what do you think will output:
123456789101112 |
Package mainimport' FMT 'func Main () { for i: = 0; i < 3 ; i++ {gofunc() {fmt. Println (i)} ()}} |
Some people say output "0 1 2", some people say output "3 3 3".
But there's actually nothing out there. This is because the main goroutine is executed immediately, and it does not wait for the generated goroutine to execute.
Program execution begins by initializing the main, and then invoking the function main. When this function invocation returns, the program exits. It does not wait for the other (Non-main) Goroutines to complete.
You can add the following line and wait for all of the goroutine
execution to finish:
Because the SELECT statement is blocked, all previously generated goroutine will be executed.
You may find that the program will end up with one of the following error messages:
1 |
Fatal error: All Goroutines is asleep-deadlock! |
It means that all the goroutine have been executed, your select is still there, there is no case waiting for you to execute, so there is the possibility of a deadlock. Go forced to kill this wait and throw an error. So you can ignore this error, it has no effect on our previous program execution.
If you don't want to see this error, you can use it sync.WaitGroup
, or, like a processing method in other languages, read a value from the command line causing the main goroutine to block or add a line of time. Sleep allows main goroutine to hibernate for a longer period of time:
1 |
Os. Stdin.read (make ([]byte, 1)) |
So, with the above line, what will it output?
The answer is "3 3 3", why?
This is because of the situation with closure (the concept of closure closure is used in many languages. In go, you can simply assume that an anonymous function keeps a reference to an external variable, and each iteration of the For loop uses the same variable i, so that each goroutine holds a reference to the same variable, because the main gororutine executes quickly, Three goroutine haven't had time to execute, and when they do, I already equals 2, so they all print out two.
We can change it a little bit so that main goroutine don't do that fast, pausing for 1 seconds per iteration:
1234567 |
for I: = 0; I < 3; i++ {gofunc() {fmt. Println (i)} () time. Sleep(1 * time. Second)} |
The result of this code output is "0 1 2". Because the generated go routine has an opportunity to execute when the main goroutine is paused.
However, we cannot precisely control the execution of the goroutine, and if we expect the output to always use the value of the current iteration, it can be transformed into the following:
12345 |
for I: = 0; I < 3; i++ {gofuncint) {fmt. Println (v)} (i)} |
The output is "2 0 1" (The execution order of Goroutine may be different, but if you see the last section of the analysis, this execution order can also make sense, the last output 2 of the Goroutine as Runnext priority, The output 1 Goroutine was originally in the Runnext position, unfortunately was squeezed out, placed in the local queue of the team tail.
If you don't want to transform an anonymous function, you can create a local variable like this:
123456 |
for I: = 0; I < 3; i++ {i: = igofunc() {fmt. Println (i)} ()} |
Output "2 0 1", note that we use a local variable of the same name shadow the iteration of the variable i.
Reference:
- Https://golang.org/doc/faq#closures_and_goroutines
What is Goroutine?
Goroutine is a unique concept in the Go language.
Concurrent and multithreaded programming is always considered difficult, much of it due to their implementation, and the control of threads and concurrent access is complex. The basis for Go language concurrency is goroutine and channel.
These concepts come from the communication sequential Process (CSP), a well-known computer scientist C.a.r.hoare.
In this language, a concurrent system consists of several sequential processes that run in parallel, and each process cannot assign values to variables of other processes. Collaboration between processes can only be achieved through a pair of communication primitives: Q->x means entering a value from the process Q into the variable x; p<-e means sending the value of the expression E to process P. When the P process executes q->x, while the Q process executes P<-E, communication occurs, and the value of E is transferred from the Q process to the variable x of the P process.
Occam and Erlang are based on the theory of CSP implementation of the concurrency model.
Go also borrowed from the theory of CSP, but different, the biggest difference is the go display to use the channel, the channel in Go is the first class of objects, Goroutine communication completely through the channel implementation.
The distribution of messages in the CSP model is instantaneous and synchronous, and the Go Channel is different, and the message is cached in the channel.
One interesting item I saw was the use of the go language to implement the example in the Hoare paper, where interested friends can watch carefully, CSP.
Fortunately, the details of these implementations are not necessary for the learning and application of the go language, and for language designers it is worth comparing and researching and writing papers.
However, for developers, at least you should understand the difference between Goroutine and threading, why a go app can have thousands of goroutine for threads.
Goroutine vs Thread
Java threads are best known for threading. We compare from three aspects:
1. Memory consumption
Goroutine does not require too much memory footprint, initially only 2kB of stack space (since Go 1.4), as needed can grow.
The thread is initially 1MB, and a guard page is assigned.
In the process of using Java Development Server often encounter the problem of request per thread, if you assign a thread for each request, the server will soon die because of large concurrency, because the memory is not enough, So many Java frameworks, like Netty, use a thread pool to handle requests without any thread growth.
In the case of using goroutine, you can see that the official Net/http library is handled using the request per Goroutine mode, and memory footprint is not a problem.
2. Creation and destruction of objects
The creation and destruction of threads is certainly a cost because resources need to be requested/returned from the OS.
The creation and destruction of goroutine is less expensive because it is a user-state operation. And the go language does not provide goroutine manual management.
3. Switching time
When a thread is blocked, other line inlet may be executed, which is called thread switching. When switching, the scheduler needs to save the state of the currently blocked thread, restoring the state of the thread to be executed, including all registers, 16 universal registers, program counters, stack pointers, segment registers, 16 XMM registers, FP coprocessor, 16 AVX registers, all MSR, and so on.
Goroutine save and restore requires only three registers: program counter, stack pointer, and DX register. Because Goroutine share heap space, do not share the stack space, so just put the goroutine stack pointer and program to save and restore the information there, the cost is very low.
By analyzing the above three aspects, you can see that goroutine has more advantages than threading. In fact, go uses a small number of threads to execute these goroutine, and through the GOMAXPROCS environment variable you can control how many threads can execute the user-state code concurrently. Threads that are blocked because of system calls are not limited by this variable. In the previous version of Go, this variable is 1, and since go 1.5 its default value is the CPU's number of cores.
The process has its own independent heap and stack, neither sharing the heap nor sharing the stack, and the process is dispatched by the operating system.
threads have their own separate stacks and shared heaps, shared heaps, non-shared stacks, and threads are also dispatched by the operating system (standard threads Yes).
The threads share the heap as a thread and do not share the stack, and the process is displayed by the programmer in the code of the coprocessor.
Goroutine vs Coroutine
Two similar, is a shared heap, do not share the stack, switch needs to save and restore the stack information.
But Coroutine needs to show control of the Coroutine transformation, and the programmer needs to invoke the execution of the current coroutine at the point where it is switched yield
so that other coroutine can be executed in this thread, When a paused coroutine resumes execution, it resumes execution where it was last paused, rather than executing from scratch like a normal function. Look at a LUA coroutine code:
123456789101112131415161718 |
function foo (a) Print("foo"Areturn Coroutine. Yield (2*A)EndCO =Coroutine. Create ( function (A, b) Print("Co-body", A, B)LocalR = Foo (A +1)Print("Co-body", R)LocalR, S =Coroutine. Yield (A+b, a-B)Print("Co-body", R, s)returnB"End" End)Print("Main",Coroutine. Resume (CO,1,Ten))Print("Main",Coroutine. Resume (CO,"R"))Print("Main",Coroutine. Resume (CO,"x","Y"))Print("Main",Coroutine. Resume (CO,"x","Y")) |
Output:
123456789 |
co-body1 foo 2main true 4co- Body rmain trueone -9co-body x ymain true Ten Endmain false cannot resume dead coroutine |
You can see that the coroutine switch is triggered by yield in the code.
Goroutine is also performed by a set of threads, pauses, and continues, but this control is not scheduled by the programmer, it is controlled by the Go runtime background. Goroutine scheduling cannot be performed manually, which is the biggest difference from coroutine. When Goroutine is blocked, it is possible to allow the degree of thread to be executed by other goroutine, Goroutine may suspend its own operation in the following situations:
- Call runtime. Gosched () puts the current goroutine into the global queue
- Call runtime. Goexit, terminating G mission
- Network Read
- Sleep
- Channel operations
- Calling an object in the Sync pack to block
- Other gouroutine blocked situations, such as IO read, empty infinite loop, long time consuming thread execution Goroutine
Cororutine can also be implemented using Goroutine and channel, such as the following code:
12345678910111213 |
func Chan string "One" "both" "three"} func Make (chanstring) Go // One // both //Three} |
Reference
- Https://talks.golang.org/2012/concurrency.slide
- Https://talks.golang.org/2012/waza.slide
- http://blog.nindalf.com/how-goroutines-work/
- Http://stackoverflow.com/questions/18058164/is-a-go-goroutine-a-coroutine
- Http://www.golangpatterns.info/concurrency/coroutines
- Https://golang.org/doc/faq#goroutines
- Https://blog.golang.org/share-memory-by-communicating
- Http://stackoverflow.com/questions/32651557/golang-main-difference-from-csp-language-by-hoare
- http://www.informit.com/articles/printerfriendly/1768317
- Https://en.wikipedia.org/wiki/Coroutine
- http://www.jianshu.com/p/36e246c6153d
- https://github.com/golang/go/issues/4056
- Http://stackoverflow.com/questions/28354141/c-code-and-goroutine-scheduling
Dispatch of Goroutine
Goroutine Dispatch (scheduling) Articles online very much, and the analysis is very deep. This article focuses on some of the details.
The Goroutine Scheduler has three important data structures, all of which are named by a single letter: G, P, M, because Golang and implementation bootstrap, so most of the code is implemented by the go itself, a small part of the assembly to achieve, because you have been the basic knowledge of go, So you can look at these implementations of code without feeling particularly difficult.
- m represents the system thread (machine), which is managed by the operating system.
- G represents goroutine, including stack/instruction pointers and other useful information for scheduling goroutine.
- P represents the processor (processor), noting that it is not the CPU processor, but the dispatch processor, which contains the context of the dispatch.
The data structure definition of these three objects is defined in the src/runtime/runtime2.go of Go source code, and also includes a very important data structure Schedt.
P must be combined with M to execute G, but the two are not exactly 1:1 correspondence, usually the number of P is fixed and the CPU number of cores (Gomaxprocs parameter), M is created on demand, for example, when M is stuck in a system call for a long time, p will be taken back by the monitoring thread, Go to create or wake up another m to execute, so the number of M will increase, there may be some blocked m in the system.
When a G is created, it may be placed into a local queue of p or a global queue:
Since the execution time of the goroutine will not be the same, goroutine can not be evenly distributed in all P's local queue, if one of the p executes quickly, there is no other gouroutine in its queue to be executed, It will take a batch of goroutine from the global queue.
If there is no goroutine to execute in the global queue, then this p may have to "steal" some goroutine from the other p.
The purpose of this design is not to let part of P busy to die, the other part of P is very quiet, this is a balance process.
The
compiler will "go func (...) {}(......)" Translated into "Newproc" calls, this method is defined in Runtime/proc.go:
1234567891011121314 |
// Create a new G running FN with siz bytes of arguments. //Put it on the queue of G ' s waiting to run. //the compiler turns a go statement into a call to this. //cannot split the stack because it assumes that the arguments //is available sequentially after &fn; they would not being //copied if a stack split occurred. //go:nosplit func newproc (siz int32 , fn *funcval) {argp: = Add (unsafe. Pointer (&FN), sys. ptrsize) PC: = Getcallerpc (unsafe. Pointer (&siz)) Systemstack (func () {Newproc1 (FN, (*uint8 ) (ARGP), Siz, 0 , PC)})} |
Its primary logic for creating G is implemented in Newproc1 and calls the runqput
G that will be created into the queue. Note that G can be reused if there is a reuse of G, then select one, otherwise create a new one, and it also has a local reuse linked list and a global reuse linked list.
runqput
First attempt to put G into the local queue of P local queue, and without setting "-race", you might try to put this g in p.runnext as the next priority G, and the original runnext back to the end of the team. If the local queue is full, it is placed into the global queue and a portion of the local queue is also placed in the global queue.
The priority of the task queue is divided into three types: P.runnext, P.RUNQ, and global schedt.runq.
schedule
method is used to implement Goroutine invocation, you can search for it in the Proc.go file.
If you browse the implementation of the schedule () method, you can see that every time you try to get G out of the global queue, you avoid g in the global queue from not having a chance to execute.
Then try to get g in the local queue, select G according to the priority, first P.runnext, then fetch from the head of the queue.
If the local queue does not have G, the calling findrunnable
method is fetched from somewhere else, which is a block method until a G is obtained.
Findrunnable first gets from the local queue (Runqget method), then gets from the global queue (Globrunqget), and then checks the goroutine of Netpoll,
If not, randomly select a p, steal some tasks come over (Runqsteal method, if "hungry", even the runnext of others have stolen).
You can view each of the selected methods in the specific get process.
You can see the details of the go Scheduler through the Schedtrace debug parameters:
1 |
Gomaxprocs=1 godebug=schedtrace=1000 ./example |
Detailed article can view my translation of William Kennedy's Scheduler tracing in Go
Reference
- https://golang.org/pkg/runtime/
- http://www.cs.columbia.edu/~aho/cs6998/reports/12-12-11_ Deshpandesponslerweiss_go.pdf
- https://morsmachine.dk/go-scheduler
- http://studygolang.com/ articles/6070
- http://www.slideshare.net/matthewrdale/demystifying-the-go-scheduler
- https:// Github.com/qyuhen/book/blob/master/go%201.5%20%e6%ba%90%e7%a0%81%e5%89%96%e6%9e%90.pdf
- https:// www.goinggo.net/2015/02/scheduler-tracing-in-go.html
- https://tiancaiamao.gitbooks.io/go-internals/ content/zh/05.1.html
- http://dave.cheney.net/2015/08/08/performance-without-the-event-loop