协程(Go程) goroutine
协程:co-routine
Golang对协程的解决:
- 协程 => goroutine,内存几KB,能够大量应用
- 灵便调度,可常切换
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 (们) 能够由调度器创立.
调度器的设计策略
复用线程
- work stealing机制:未充分利用的处理器会被动去寻找其余处理器的线程并
窃取
一些 hand off机制:
如果正在运行的协程 G1 阻塞了,然而其余的调度器都在解决本人的业务,没有工夫去偷 work stealing 这阻塞的 G1 下面队列中的其余协程,咱们就唤醒一个线程,或者开启一个线程,将咱们之前的在调度器和其余没有阻塞的协程切换过来;G 阻塞完结后,如果还要运行,就放到其余调度器 P 下面的协程队列中,如果不执行,就将 M1 线程休眠或销毁即可
- work stealing机制:未充分利用的处理器会被动去寻找其余处理器的线程并
- 利用并行:通过GOMAXPROCS限定P的个数
- 抢占:每个goroutine最多执行10ms(co-routine只能期待CPU被动开释)
- 全局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程会始终阻塞
缓冲
无缓冲的channel
c := make(chan int)
有缓冲的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
- channel不像文件一样常常去敞开,只有的确没有任何发送数据了,或想显式完结range循环,才去敞开channel;
- 敞开channel后无奈再向channel发送数据(引发panic谬误后导致接管立刻返回零值)
- 敞开channel后,能够持续从channel中接收数据
- 对于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的弊病
- 没有版本控制概念
- 无奈同步统一第三方版本号
- 无奈指定以后我的项目援用的第三方版本号
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初始化我的项目
创立go.mod
go mod init 以后模块名称(导包时应用)
导入包(下载门路:
$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