共计 4321 个字符,预计需要花费 11 分钟才能阅读完成。
协程(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 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 程会始终阻塞
缓冲
-
无缓冲的 channel
c := make(chan int)
-
有缓冲的 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
- channel 不像文件一样常常去敞开,只有的确没有任何发送数据了,或想显式完结 range 循环,才去敞开 channel;
- 敞开 channel 后无奈再向 channel 发送数据(引发 panic 谬误后导致接管立刻返回零值)
- 敞开 channel 后,能够持续从 channel 中接收数据
- 对于 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 的弊病
- 没有版本控制概念
- 无奈同步统一第三方版本号
- 无奈指定以后我的项目援用的第三方版本号
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 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