Go language goroutine concurrency model

Brief introduction

stack

The initial OS thread stack is 2MB. Go languages, each goroutine dynamic expansion mode, the initial 2KB, demand growth, maximum 1G. In addition GC will shrink stack space.
BTW, growth expansion comes at a price, you need to copy data to the new stack, so the initial 2KB may be some performance issues.
More details about the stack, you can see the big brother of the article. Chat goroutine stack

management

Scheduling, and life cycle management of user threads are user level, Go language realize their own, without the aid of OS system calls, reduce system resource consumption.

G-M-P

Go language model using two threads, i.e., user threads and kernel threads KSE (kernel scheduling entity) is M: N's. Goroutine or will eventually handed over to OS thread execution, but requires an intermediary to provide context. This is GMP model

  • G: goroutine, similar to the process control block, the stack stores information, status, id, and other functions. P G can be bound to be scheduled.
  • M: machine, OS thread, after binding effective P, into the schedule.
  • P: logical processor, stores various queues G. For G, P is the cpu core. For M, P is the context. The number of P by GOMAXPROCSsetting the maximum 256.
  • sched: scheduler, save GRQ, midle M idle queue, pidle P idle queue and lock information

Here Insert Picture Description

queue

Go scheduler has two different run queue:

  • The GRQ, global run queue, not yet assigned to the G P
  • The LRQ, local run queue, the LRQ Each has a P, P to G are assigned to manage the execution

status

go1.10\src\runtime\runtime2.go
  • _Gidle: dispensing the G, but not initialized
  • _Grunnable: In the run queue run queue, LRQ or GRQ
  • _Grunning: running command has its own stack. Runq run queue is not allocated to the M and P
  • _Gsyscall: executing the syscall, not the user instruction, not the runq, points to M, P M to find the idle
  • _Gwaiting: block. Not RQ, but may wait in the queue wait queue channel of
  • _Gdead: unused. In the gfree list P, not runq. idle idle
  • _Gcopystack: stack expansion or contraction gc

Context switching

Go The scheduler context switch events.

  • go keyword, create goroutine
  • gc garbage collection, gc is goroutine, so take time slice
  • system call system call, block current G
  • sync synchronization, block current G

Dispatch

Scheduling purposes is to prevent clogging of M, leisure, switching system processes.
See Golang - [Part II] Scheduling Analysis

Asynchronous call

Linux can be invoked by the network epoll achieved collectively network poller N (Net Poller).

  1. G1 runs on M, P LRQ the other three G, N idle;
  2. G1 network IO, is thus moved to the N, M G continues execution from taking another LRQ. G2 was such context switch to M;
  3. G1 end network request, the response is received, the LRQ G1 is moved back, waiting to switch execution to M.

Synchronous call

File IO operations

  1. G1 runs on M1, P LRQ the other three G;
  2. G1 synchronous calls, congestion M;
  3. Scheduler M1 and P is separated, at this time only M1 G1, no P.
  4. The M2 binding idle P, and G2 from the select switch LRQ
  5. G1 end plugging operation, moved back LRQ. M1 idle standby.

Task steal

Above all to prevent clogging of M, M idle task is to prevent theft

  1. Two P, P1, P2
  2. If P1 are executed G-finished, LRQ empty, P1 began to steal task.
  3. The first case, there LRQ P2 G, P2 from P1 is half the stolen LRQ G
  4. The second case, P2 no LRQ, P1 stolen from GRQ.

g0

Each has a special M G, g0. Used to perform scheduling, gc, stack management and other tasks, so g0 stack called scheduling stack. g0 stack does not automatically increase, not gc, os stack from the thread.

code

go1.10\src\runtime\proc.go

new

// The minimum size of stack used by Go code
    var _StackMin = 2048

func newproc1(fn *funcval, argp *uint8, narg int32, callerpc uintptr) {
    _g_ := getg()
    _p_ := _g_.m.p.ptr()
    
    newg := gfget(_p_)
    if newg == nil {
        newg = malg(_StackMin)
    }    
    
    newg.startpc = fn.fn
    
    runqput(_p_, newg, true)
    
    if atomic.Load(&sched.npidle) != 0 && atomic.Load(&sched.nmspinning) == 0 && mainStarted {
        wakep()
    }
}
  1. Get the current G
  2. Get current P G,
  3. G acquired from gfree P's, to avoid re-create a little pool of meaning
  4. If G is not reusable, re-created parameter indicates stack size, starting 2KB, support for dynamic expansion
  5. The enqueue G, P placed in the LRQ; due to work stealing mechanism can steal other P P G from this
  6. If runq full (length 256), it is placed in the GRQ, in the sched
  7. Additional attempts to execute G P

start

G can not run by itself, it must be run by M

func mstart() {
    mstart1(0)
    
    mexit(osStack)
}

func mstart1(dummy int32) {
    _g_ := getg()

    if _g_ != _g_.m.g0 {
        throw("bad runtime·mstart")
    }    
    
    schedule()    
}

Adoption of the M scheduling, execution G

schdule

// One round of scheduler: find a runnable goroutine and execute it.
// Never returns.
func schedule() {
    _g_ := getg()
    
    var gp *g
    
    gp, inheritTime = runqget(_g_.m.p.ptr())
    
    execute(gp, inheritTime)
}

reference

Goroutine concurrent scheduling model depth analysis & hand line and a pool coroutine
how Golang of goroutine is achieved?
Golang - [Part II] Scheduling Analysis

Published 158 original articles · won praise 119 · views 810 000 +

Guess you like

Origin blog.csdn.net/u013474436/article/details/103824154