乐趣区

关于golang:Go语言快速入门笔记02

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

管道 channel

定义 channel
package main
import "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 main
    import (
        "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 main
import "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 main
import "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 main
import "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)
}
后果:1
1
2
4
8
16
32
64
128
256
quit

备注:无缓冲的 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 main
import (
    "fmt"
    "github.com/aceld/zinx/ziface"
    "github.com/aceld/zinx/znet"
)
//ping test 自定义路由
type PingRouter struct {znet.BaseRouter}
//Ping Handle
func (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
退出移动版