Go Language Quick Start Notes 02

Go routine

Protocol: co-routine

Golang's handling of coprocesses:

  1. Protocol=> goroutine, several KB of memory, can be used in large quantities
  2. Flexible scheduling with frequent switching

GMP

Related data:

Go work-stealing scheduler

goroutine basic model and scheduler strategy in detail

Go has a M:N scheduler that can take advantage of multicore processors. At any time, M goroutine s need to be scheduled on N OS threads running on a maximum number of GOMAXPROCS processors.

G:goroutine protocol

P:processor processor processor (maximum GOMAXPROCS)

M:thread thread

There is a P-related local and global goroutine queue. Each M should be assigned to a P. If blocked or in system calls, P may not have M. At any time, there is at most one P in the number of GOMAXPROCS. At any time, only one M can run per P. If needed, more M can be created by the scheduler.

Scheduler Design Policies

  1. Reuse Threads

    1. work stealing mechanism: underutilized processors actively look for threads from other processors and steal some
    2. Handoff mechanism:

      If a running protocol G1 Blocked, but other schedulers are handling their own business and don't have time to steal work stealing This blocked G1 With the other protocols in the queue above, we either wake up a thread or start a thread to switch our previous scheduler and other non-blocked processes over;G After the blocking is over, if you want to run it, put it on another scheduler P In the above protocol queue, if not executed, the M1 Thread can sleep or be destroyed
  2. Utilize Parallelism: Limit the number of P through GOMAXPROCS
  3. Preemption: Each goroutine can execute up to 10 ms (co-routine can only wait for CPU to actively release)
  4. Global G Queue: When P's local queue is empty, M first takes G from the global queue to P's local queue, and then steals it from other P's

Create goroutine

package main
import (
    "fmt"
    "time"
)
//Sub go range
func newTask(){
    i := 0
    for {
        i++
        fmt.Printf("newTask Goroutine i=%d\n", i)
        time.Sleep(1 * time.Second)
    }
}
//Main go
func main() { 
    //Create a subroutine to execute newTask()
    go newTask()
    fmt.Println("main Goroutine exit")//After the main go, the subgo will be destroyed
    i := 0
    for {
        i++
        fmt.Printf("main Goroutine i=%d\n", i)
        time.Sleep(1 * time.Second)
    }
}

Anonymous goroutine

package main
import (
    "fmt"
    "time"
    "runtime"
)
func main() { 
    go func () {
        defer fmt.Println("A.defer")
        func () {
            defer fmt.Println("B.defer")
            runtime.Goexit()//Exit the current go
            //return // Exit only the current closure, not the current go
            fmt.Println("B")
        }()
        fmt.Println("A")
    }()
    for {
        time.Sleep(1 * time.Second)//Dead cycle to prevent the main go from exiting
    }
}

Anonymous goroutine with parameters

package main
import (
    "fmt"
    "time"
)
func main() { 
    go func (a int,b int) bool {
        fmt.Println("a=",a,",b=",b)
        return true
    }(10, 20)
    for {
        time.Sleep(1 * time.Second)//Dead cycle to prevent the main go from exiting
    }
}

Pipeline channel

Define channel
package main
import "fmt"
func main() { 
    c := make(chan int)
    go func () {
        c <- 666 //Send 666 to c
    }()
    num := <- c  //Receive data from c and assign to num
    //Num, ok: = <-c //Check if the channel is closed or empty
    fmt.Println("num=", num)//num= 666
}

Note: If num does not receive data from c, the go will always be blocked

buffer
  1. Unbuffered channel

    c := make(chan int)
  2. Buffered channel: When the channel is full, writing data to it will block; When channel is empty, fetching data will also block

    c := make(chan int, 3) //Buffer capacity 3
    package main
    import (
        "fmt"
        "time"
    )    
    func main() { 
        c := make(chan int, 3)
        go func () {
            defer fmt.Println("son go End of process")
            for i := 0; i < 4; i++ {
                c <- i
                fmt.Println("son go The process is running i=",i)
            }
        }()
        time.Sleep(1 * time.Second)
        for i := 0; i < 4; i++ {
            num := <- c
            fmt.Println("num=", num)
        }
    }
Turn off channel
  1. Channels don't shut down as often as files. They do not shut down channel s unless there is really no data to send or if you want to explicitly end range loops.
  2. Cannot send data to channel after channel is closed (cause receive to return zero immediately after panic error is raised)
  3. After closing the channel, you can continue to receive data from the channel
  4. For nil channel, both send and receive will be blocked
package main
import "fmt"
func main() { 
    c := make(chan int, 3)
    go func () {
        for i := 0; i < 5; i++ {
            c <- i
        }
        close(c)//Turn off channel
    }()
    for {
        //ok for true means channel is not closed (or has data), false means no data and is closed
        if data, ok := <- c; ok {
            fmt.Println(data)
        }else{
            break
        }
    }
    fmt.Println("main End")
}
channel and range

range can iterate over and over channel s (blocking)

package main
import "fmt"
func main() { 
    c := make(chan int)
    go func () {
        for i := 0; i < 10; i++ {
            c <- i
        }
        close(c)//Turn off channel
    }()
    for data := range c {
        fmt.Println(data)
    }
}
channel and select

Only one channel state can be monitored by the next go in a single process, and select can complete the monitoring of multiple channels (typically with a for dead loop)

select {
    case <- chan1:  //If chan1 successfully reads data (readable), perform the case processing
    case chan2 <- 1://If chan2 writes successfully (writable), perform the case processing
    default:        //If none of the above succeeds, default processing is performed
}

case

package main
import "fmt"
func test(c, quit chan int){
   x, y := 1, 1
   for {
       select {
       case c <- x://c can be written, then execute the case
           x = y
           y = x + y
       case <- quit://quit has a value, then execute the case
           fmt.Println("quit")
           return    
       }
   }
}
func main() { 
   c := make(chan int)
   quit := make(chan int)
   //Sub go range
   go func () {
       for i := 0; i < 10; i++ {
           fmt.Println(<-c)//Read data from c, otherwise block
       }
       quit <- 0
   }()
   //Main go
   test(c, quit)
}
Result:
1
1
2
4
8
16
32
64
128
256
quit

Note: Unbuffered channel reads and writes will block

Go Modules

GOPATH pull dependency
go get -u xxx
Disadvantages of GOPATH
  1. There is no version control concept
  2. Unable to synchronize consistent third-party version numbers
  3. The third-party version number referenced by the current project cannot be specified
Go Modules mode
commandEffect
go mod initGenerate go.mod file
go mod downloadDownload all dependencies specified in the go.mod file
go mod tidyOrganize existing dependencies
go mod graphView existing dependency structures
go mod editEdit go.mod file
go mod vendorExport all dependencies of the project to the vendor directory
go mod verifyVerify whether a module has been tampered with
go mod whySee why you need to rely on a module
environment variable

View through go env

GO111MODULE

Switch on GO Modules, recommended to enable

Open mode:

go env -w GO111MODULE=on
GOPROXY

Set up Go module proxy

Set up Qiniuyun agent:

go env -w GOPROXY=https://goproxy.cn,direct

direct return source: if the agent cannot find it, it will pull from the source

GOSUMDB

Ensure that the pulled module version data is not tampered with (recommended to open)

GONOPROXY/GONOSUMDB/GOPRIVATE

Configure private libraries (GOPRIVATE is recommended directly)

go env -w GOPRIVATE="*.example.com"

Go Modules Initialization Project

  1. Create go.mod

    go mod init Current module name(Use when importing packages)
  2. Import package (download path: $GOPATH/pkg/mod)

    go get Package address (example: github.com/aceld/zinx/znet)

    go.sum: Lists all module versions that the current project depends on directly or gradually, ensuring that future versions of the project depend on are not tampered with

Example:

package main
import (
    "fmt"
    "github.com/aceld/zinx/ziface"
    "github.com/aceld/zinx/znet"
)
//ping test custom routing
type PingRouter struct {
    znet.BaseRouter
}
//Ping Handle
func (this *PingRouter) Handle(request ziface.IRequest) {
    //Read client data first
    fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData()))
    //Write back ping...ping...ping
    err := request.GetConnection().SendBuffMsg(0, []byte("ping...ping...ping"))
    if err != nil {
        fmt.Println(err)
    }
}
func main() {
    //1 Create a server handle
    s := znet.NewServer()
    //2 Configure Routing
    s.AddRouter(0, &PingRouter{})
    //3 Open Services
    s.Serve()
}
Modify package dependencies
go mod edit -replace=xxx

Keywords: Go

Added by Katanius on Thu, 02 Dec 2021 03:06:07 +0200