协程(Go程) goroutine
协程:co-routine
Golang对协程的处理:
- 协程 => goroutine,内存几KB,可以大量使用
- 灵活调度,可常切换
GMP
相关资料:
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