Go language goroutine analysis

Go inside Goroutine is a lightweight thread - coroutine. Relative thread, coroutine advantage is that it is very lightweight, carry out the cost of context switching is very small. For a goroutine, each structure has a G sched attribute is used to save its context. In this way, goroutine you can easily switch back and forth. Due to its context switch occurs in the user mode, kernel mode do not have to enter, so fast. And only the current goroutine the PC, SP and other small amount of information needs to be saved.

However, in Go, each concurrent execution unit is a goroutine. When we started running a Go program, its function main entrance is actually running in a goroutine.

Communication between Goroutine

Go through programs written in different languages ​​goroutine run, but between goroutine are independent of each other, each running in a different context. Communication between each goroutine need the help channel, channel Go language is a communication mechanism. Channel Go language is a reference type, function by make, we can easily declare a channel.

ch := make(chanstring)

Channel single direction, both directions of the points:

  • chan int, the two sides can be used to send and receive data.
  • chan <- int, in one direction, only be used to transmit data
  • <- chan int, in one direction only to receive data

In addition, channel there was the buffer cache without distinction. Create a channel buffered by the make function.

ch := make(chanstring, n)

Unbuffered channel receiving operation to ensure the synchronization of each transmitted data. The channel buffered decoupling operation between sending and receiving, not only affect the performance of such procedures may also cause deadlocks.

The scheduler sheduler

Run each goroutine is determined by the Go language scheduler (scheduler) of.

Let me talk about the operating system thread scheduling. There is a kernel function sheduler by POSIX, every few ms will be executed once. Each execution, will suspend the current thread of execution, it registers while preserving the information, and then view the list of threads running under the decision of a thread, it will resume its site and register information from memory and start executing. There is a context switch between different threads, which includes a save state to the memory of the user thread, another thread to restore the information to the register, but also to update sheduler associated data structures. These operations are very time-consuming.

Runtime Go language has its own sheduler, through which we can schedule the m goroutine on the n operating system threads. Go sheduler fact of the operating system sheduler is very similar, except that it only concerned goroutine schedule. The operating system sheduler difference is, Go is sheduler does not use hardware timers, when a goroutine called time.Sleep, triggering a channel operation or use mutex, scheduler would be the goroutine sleep, wake up and then go to another goroutine, which scheduling profiles without context switching between its thread scheduling cost than the operating system is much smaller.

Go realize scheduling, involving several important data structures. Run-time library use these data structures to achieve operational scheduling, management goroutine and physical threads goroutine of. These data structures are structures G, the structure M, the structure P, and Sched structure. This structure is defined in three files runtime / runtime.h in Sched defined at runtime / proc.c in. In the Go language scheduler through a GOMAXPROCS variables to determine the number of operating system threads to run the Go program, the default value of CPU cores.

Structure G

G is goroutine abbreviation equivalent operating system process control block, the control structure goroutine of this is, is an abstract of goroutine. Including goid goroutine this is the ID, status is the status of this goroutine such Gidle, Grunnable, Grunning, Gsyscall, Gwaiting, Gdead like.

structG
{
    uintptr    stackguard;    // 分段栈的可用空间下界
    uintptr    stackbase;    // 分段栈的栈基址
    Gobuf    sched;        //进程切换时,利用sched域来保存上下文
    uintptr    stack0;
    FuncVal*    fnstart;        // goroutine运行的函数
    void*    param;        // 用于传递参数,睡眠时其它goroutine设置param,唤醒时此goroutine可以获取
    int16    status;        // 状态    Gidle,Grunnable,Grunning,Gsyscall,Gwaiting,Gdead
    int64    goid;        // goroutine的id号
    G*    schedlink;
    M*    m;        // for debuggers, but offset not hard-coded
    M*    lockedm;    // G被锁定只能在这个m上运行
    uintptr    gopc;    // 创建这个goroutine的go表达式的pc
...
};

G is part of the structure shown above fields. It can be seen, which contains information stackbase stack and stackguard, there is a function of the information fnstart run. These enough to become an executable unit, and so long as a CPU can run.

When goroutine handover, context information is stored in sched domain structure. goroutine is called lightweight thread or coroutine, and do not have time to switch into the operating system kernel, so save process is very lightweight. Look at the structure of G Gobuf, in fact, save only the current stack pointer, program counter, and goroutine itself.

structGobuf
{

    // The offsets of these fields are known to (hard-coded in) libmach.
    uintptr    sp;
    byte*    pc;
    G*    g;
...
};

G is the record in order to restore the current G pointer goroutine structural body, using a library resident register extern register G * g running, this is the pointer to the current configuration goroutine body G. This is done to information goroutine quickly access, for example, to achieve stack Go and do not use the% ebp register, but it can> stackbase quickly get through g-. "Extern register" is a special store by the 6c, 8c, etc. to achieve. It is actually on ARM register; other platforms is a slot index thread local storage by segment register. In linux system, used for g and m are 0 (GS) and 4 (GS). Note that, the linker will change the output of the compiler according to a specific operating system, e.g., 6l / linux will next 0 (GS) is rewritten as -16 (FS). Each link to Go program files must contain runtime.h C header files, so avoid using the C compiler knows only register.

Structure M

M is the abbreviation of the machine, the machine is an abstract, each m is mapped to a physical thread operating system. Must be associated with the P M can perform the Go, but when it is blocked or system call processing, may not be required associated P.

structM
{
    G*    g0;        // 带有调度栈的goroutine
    G*    gsignal;    // signal-handling G 处理信号的goroutine
    void    (*mstartfn)(void);
    G*    curg;        // M中当前运行的goroutine
    P*    p;        // 关联P以执行Go代码 (如果没有执行Go代码则P为nil)
    P*    nextp;
    int32    id;
    int32    mallocing; //状态
    int32    throwing;
    int32    gcing;
    int32    locks;
    int32    helpgc;        //不为0表示此m在做帮忙gc。helpgc等于n只是一个编号
    bool    blockingsyscall;
    bool    spinning;
    Note    park;
    M*    alllink;    // 这个域用于链接allm
    M*    schedlink;
    MCache    *mcache;
    G*    lockedg;
    M*    nextwaitm;    // next M waiting for lock
    GCStats    gcstats;
...
};

Similarly and G, M is also alllink M in all the domains allm list. lockedg is some cases, G lock without switching to another M M go in this run. M also has a MCache, M is the current memory cache. M and G are also resident as a register variable, represents the current M. At the same time there is a plurality M, indicating the presence of a plurality of simultaneous physical threads. There are two structures M G is the need to look at, one structure curg, M represents the structure of the current binding G. Another is g0, is scheduled with goroutine stack, which is a rather special goroutine. Common goroutine stack is allocated on the heap can increase the stack, and the stack is M g0 corresponding thread's stack. All scheduling related code, will switch to the first re-executed goroutine stack.

Structure P

A data structure Go1.1 newly added, which is an abbreviation of the Processor. P is added to the structure in order to improve concurrency Go programs, better scheduling. M stands for OS threads. Go resources required when code is executed on behalf of P. Go executed when M codes, which need to associate a P, when M is idle, or when the system call, it needs to P. There are just a GOMAXPROCS P. All P is organized as an array, to achieve theft workflow scheduler on P.

structP
{
    Lock;
    uint32    status;  // Pidle或Prunning等
    P*    link;
    uint32    schedtick;  // 每次调度时将它加一
    M*    m;    // 链接到它关联的M (nil if idle)
    MCache*    mcache;
    G*    runq[256];
    int32    runqhead;
    int32    runqtail;
    // Available G's (status == Gdead)
    G*    gfree;
    int32    gfreecnt;
    byte    pad[64];
};

P also has a structure corresponding status: Pidle, Prunning, Psyscall, Pgcstop, Pdead. The difference is that with G, P does not exist the waiting state. The difference is that with G, P does not exist the waiting state. MCache P is moved, the structure M but also retained. Goroutine Grunnable has a queue in the P, which is a local queue of P. When the P code is executed Go, it will take priority from this own local queue, then the lock can not improve concurrency. If you find that the queue is empty, the queue to get the other half P's over, so to implement workflow stolen scheduling. This case is the need to lock the caller.

Structure Sched

Sched scheduling data structure is used to realize, in the definition of the structure of the file proc.c.

structSched {
    Lock;
    uint64    goidgen;
    M*    midle;    // idle m's waiting for work
    int32    nmidle;    // number of idle m's waiting for work
    int32    nmidlelocked; // number of locked m's waiting for work
    int3    mcount;    // number of m's that have been created
    int32    maxmcount;    // maximum number of m's allowed (or die)
    P*    pidle;  // idle P's
    uint32    npidle;  //idle P的数量
    uint32    nmspinning;
      // Global runnable queue.
    G*    runqhead;
    G*    runqtail;
    int32    runqsize;
      // Global cache of dead G's.
    Lock    gflock;
    G*    gfree;
    int32    stopwait;
    Note    stopnote;
    uint32    sysmonwait;
    Note    sysmonnote;
    uint64    lastpoll;
    int32    profilehz;    // cpu profiling rate
}

Most required information was placed on the structure M, G, and P, Sched only one shell structure. Can be seen, the idle queue which M, P of the idle queue, and a global queue ready G. Lock Sched structural body is necessary, if the P or M do some other non-local operation, they generally need to lock the scheduler.

Goroutine features

Go Runtime Goroutine is provided, is not supported by the operating system level, goroutine not use threads to achieve. goroutine is a piece of code, an entry function, and a stack in the heap assigned to it. The stack is usually small, usually 2kB, so it's very cheap, we can easily create on into ten million goroutine, this is very common. Goroutine stack size is not fixed, and this operating system thread is not the same, it can be dynamically expanded, the maximum value of up to 1GB.

Goroutine is a collaborative scheduling, if goroutine will perform for a long time, and not synchronized, then you need to take the initiative to call Gosched () to allow the CPU channel by waiting to read or write data.

Go language encapsulates asynchronous IO, it is possible to write a lot of looks like the number of concurrent server, even if we can take full advantage of multi-core CPU parallel processing by adjusting GOMAXPROCS, its efficiency is not as we use IO event-driven design, divided according to the type of transaction good appropriate proportion of the thread pool. In response time, coordinated scheduling is flawed.

Each goroutine is no identity, which is to avoid like Thread Local Storage that is rotten, the behavior of a function of the variable by itself can not decide.

The biggest advantage Goroutine is to achieve dynamic expansion of the thread pool in concurrent development, not due to the obstruction of a task lead to a deadlock. With the continuous development and improvement of its popular and multicore runtime era, its advantages will become increasingly prominent.

Let's look at an example.

Example: producers and consumers

Producers and consumers to achieve by goroutine, using the communication channel. Just a few lines of code, we ourselves do not need to write the code synchronization issues to consider thread.

Variables required prior notice, goods production and consumption is shared data, is declared as a type chan, store integer data. Then declare a random number seed to generate a pseudo random number in accordance with the system time. chan done is a type, which stores only one empty struct, its role is to ensure that the main thread after the other end goroutine.

    var goods chan int
    var r  = rand.New(rand.NewSource(time.Now().UnixNano()))//定义一个随机数种子
    vardone chan struct{}

Manufacturer Function 10 cycles, 1 to 10 sequentially written in the goods, after each completed time, random sleep 1 to 3 seconds.

funcproduce()  {
    for i:=1; i<=10; i++ {
        goods <- i
        time.Sleep(time.Duration(r.Int31n(3))*time.Second)
}
    done <-struct{}{}
}

Consumers functions, loops five times the value of the goods, every time read, random sleep from 1 to 5 seconds.

funcconsume() {
    for i:=0; i<5; i++ {
        good := <- goods
        fmt.Printf("The goods size is : %v\n",10-good+1)
        time.Sleep(time.Duration(r.Int31n(5))*time.Second)
    }
}

the main function to open a goroutine run produce function, two goroutine run consume function.

funcmain()  {

    goods =make(chanint)
    done =make(chanstruct{})
    goproduce()
    goconsume()
    goconsume()
    <- done
}
output:

    The goods size is :10
    The goods size is :9
    The goods size is :8
    The goods size is :7
    The goods size is :6
    The goods size is :5
    The goods size is :4
    The goods size is :3
    The goods size is :2
    The goods size is :1

reference

goroutine system knowledge behind
at The Go sheduler
at The Go Programming Language
Insights Go
http://witchiman.github.io/2017/04/24/go-goroutine

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

Guess you like

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