协程(Go程) goroutine

协程:co-routine

Golang对协程的解决:

  1. 协程 => goroutine,内存几KB,能够大量应用
  2. 灵便调度,可常切换

GMP

相干材料:

Go work-stealing 调度器

goroutine 根本模型和调度器策略详解

Go 有一个能够利用多核处理器的 M:N 调度器. 任何时候, M 个 goroutine 都须要在 N 个 OS 线程上进行调度, 这些线程运行在最多 GOMAXPROCS 数量的处理器上.

G:goroutine协程

P:processor处理器(最大GOMAXPROCS)

M:thread线程

有一个 P 相干的本地和全局 goroutine 队列. 每个 M 应该被调配给一个 P. 如果被阻塞或者在零碎调用中, P (们) 可能没有 M (们). 任何时候,最多只有 GOMAXPROCS 数量的 P. 任何时候, 每个 P 只能有一个 M 运行. 如果须要, 更多的 M (们) 能够由调度器创立.

调度器的设计策略

  1. 复用线程

    1. work stealing机制:未充分利用的处理器会被动去寻找其余处理器的线程并 窃取 一些
    2. hand off机制:

      如果正在运行的协程 G1 阻塞了,然而其余的调度器都在解决本人的业务,没有工夫去偷 work stealing 这阻塞的 G1 下面队列中的其余协程,咱们就唤醒一个线程,或者开启一个线程,将咱们之前的在调度器和其余没有阻塞的协程切换过来;G 阻塞完结后,如果还要运行,就放到其余调度器 P 下面的协程队列中,如果不执行,就将 M1 线程休眠或销毁即可
  2. 利用并行:通过GOMAXPROCS限定P的个数
  3. 抢占:每个goroutine最多执行10ms(co-routine只能期待CPU被动开释)
  4. 全局G队列:当P的本地队列为空时,M先从全局队列拿G放到P的本地队列,再从其它P窃取

创立goroutine

package mainimport (    "fmt"    "time")//子go程func newTask(){    i := 0    for {        i++        fmt.Printf("newTask Goroutine i=%d\n", i)        time.Sleep(1 * time.Second)    }}//主go程func main() {     //创立子go程执行newTask()    go newTask()    fmt.Println("main Goroutine exit")//主go程完结后子go程也会销毁    i := 0    for {        i++        fmt.Printf("main Goroutine i=%d\n", i)        time.Sleep(1 * time.Second)    }}

匿名goroutine

package mainimport (    "fmt"    "time"    "runtime")func main() {     go func () {        defer fmt.Println("A.defer")        func () {            defer fmt.Println("B.defer")            runtime.Goexit()//退出以后go程            //return //只退出以后闭包,不会退出以后go程            fmt.Println("B")        }()        fmt.Println("A")    }()    for {        time.Sleep(1 * time.Second)//死循环,避免主go程退出    }}

有参数的匿名goroutine

package mainimport (    "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)//死循环,避免主go程退出    }}

管道 channel

定义channel
package mainimport "fmt"func main() {     c := make(chan int)    go func () {        c <- 666 //将666发送给c    }()    num := <- c  //从c中接收数据赋值给num    //num ,ok := <- c //查看通道是否已敞开或为空    fmt.Println("num=", num)//num= 666}

注:如果num不从c中接收数据,go程会始终阻塞

缓冲
  1. 无缓冲的channel

    c := make(chan int)
  2. 有缓冲的channel:当channel已满,再向外面写数据会阻塞;当channel为空,取数据也会阻塞

    c := make(chan int, 3) //缓冲容量3
    package mainimport (    "fmt"    "time")    func main() {     c := make(chan int, 3)    go func () {        defer fmt.Println("子go程完结")        for i := 0; i < 4; i++ {            c <- i            fmt.Println("子go程正在运行 i=",i)        }    }()    time.Sleep(1 * time.Second)    for i := 0; i < 4; i++ {        num := <- c        fmt.Println("num=", num)    }}
敞开channel
  1. channel不像文件一样常常去敞开,只有的确没有任何发送数据了,或想显式完结range循环,才去敞开channel;
  2. 敞开channel后无奈再向channel发送数据(引发panic谬误后导致接管立刻返回零值)
  3. 敞开channel后,能够持续从channel中接收数据
  4. 对于nil channel,收发都会阻塞
package mainimport "fmt"func main() {     c := make(chan int, 3)    go func () {        for i := 0; i < 5; i++ {            c <- i        }        close(c)//敞开channel    }()    for {        //ok为true示意channel没有敞开(或有数据),false示意无数据且已敞开        if data, ok := <- c; ok {            fmt.Println(data)        }else{            break        }    }    fmt.Println("main完结")}
channel和range
range能够迭代一直操作channel (阻塞)
package mainimport "fmt"func main() {     c := make(chan int)    go func () {        for i := 0; i < 10; i++ {            c <- i        }        close(c)//敞开channel    }()    for data := range c {        fmt.Println(data)    }}
channel和select
单流程下一个go只能监控一个channel状态,select能够实现监控多个channel的状态(个别搭配for死循环)
select {    case <- chan1:  //如果chan1胜利读取到数据(可读),则执行该case解决    case chan2 <- 1://如果chan2写入胜利(可写),则执行该case解决    default:        //如果以上都没有胜利,则执行default解决}

案例

package mainimport "fmt"func test(c, quit chan int){   x, y := 1, 1   for {       select {       case c <- x://c能够写入,则执行该case           x = y           y = x + y       case <- quit://quit有值,则执行该case           fmt.Println("quit")           return           }   }}func main() {    c := make(chan int)   quit := make(chan int)   //子go程   go func () {       for i := 0; i < 10; i++ {           fmt.Println(<-c)//从c中读数据,否则阻塞       }       quit <- 0   }()   //主go程   test(c, quit)}后果:11248163264128256quit

备注:无缓冲的channel读写都会阻塞

Go Modules

GOPATH拉取依赖
go get -u xxx
GOPATH的弊病
  1. 没有版本控制概念
  2. 无奈同步统一第三方版本号
  3. 无奈指定以后我的项目援用的第三方版本号
Go Modules模式
命令作用
go mod init生成go.mod文件
go mod download下载go.mod文件中指明的所有依赖
go mod tidy整顿现有依赖
go mod graph查看现有的依赖构造
go mod edit编辑go.mod文件
go mod vendor导出我的项目所有依赖到vendor目录
go mod verify校验一个模块是否被篡改过
go mod why查看为什么须要依赖某模块
环境变量
通过go env查看
GO111MODULE
GO Modules的开关,举荐启用

开启形式:

go env -w GO111MODULE=on
GOPROXY
设置Go模块代理

设置七牛云代理:

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

direct回源:如果代理找不到,则会从源拉取

GOSUMDB
保障拉取到的模块版本数据未通过篡改(倡议开启)
GONOPROXY/GONOSUMDB/GOPRIVATE
配置公有库(举荐间接配置GOPRIVATE)
go env -w GOPRIVATE="*.example.com"

Go Modules初始化我的项目

  1. 创立go.mod

    go mod init 以后模块名称(导包时应用)
  2. 导入包(下载门路:$GOPATH/pkg/mod

    go get 包地址(例:github.com/aceld/zinx/znet)

    go.sum:列举以后我的项目间接或慢慢的依赖的所有模块版本,保障今后我的项目依赖的版本不会被篡改

示例:

package mainimport (    "fmt"    "github.com/aceld/zinx/ziface"    "github.com/aceld/zinx/znet")//ping test 自定义路由type PingRouter struct {    znet.BaseRouter}//Ping Handlefunc (this *PingRouter) Handle(request ziface.IRequest) {    //先读取客户端的数据    fmt.Println("recv from client : msgId=", request.GetMsgID(), ", data=", string(request.GetData()))    //再回写ping...ping...ping    err := request.GetConnection().SendBuffMsg(0, []byte("ping...ping...ping"))    if err != nil {        fmt.Println(err)    }}func main() {    //1 创立一个server句柄    s := znet.NewServer()    //2 配置路由    s.AddRouter(0, &PingRouter{})    //3 开启服务    s.Serve()}
批改包依赖关系
go mod edit -replace=xxx