Robert Griesemer、Rob Pike、Ken Thompson三位Go语言创始人,对新语言商在讨论时,就决定了 要让Go语言成为面向未来的语言。当时多核CPU已经开始普及,但是众多“古老”编程语言却不能很好的 适应新的硬件进步,Go语言诞生之初就为多核CPU并行而设计。 Go语言协程中,非常重要的就是协程调度器scheduler和网络轮询器netpoller。
G:Goroutine,Go协程。存储了协程的执行栈信息、状态和任务函数等。初始栈大小约为2~4k, 理论上开启百万个Goroutine不是问题
M:Machine Thread,对系统线程抽象、封装。所有代码最终都要在系统线程上运行,协程最终也是代码,也不例外
P:Go1.1版本引入,Processor,虚拟处理器
- 可以通过环境变量GOMAXPROCS或runtime.GOMAXPROCS()设置,默认为CPU核心数
- P的数量决定着最大可并行的G的数量
- P有自己的队列(长度256),里面放着待执行的G
- M和P需要绑定在一起,这样P队列中的G才能真正在线程上执行
1、使用go func创建一个Goroutine g1
2、当前P为p1,将g1加入当前P的本地队列LRQ(Local Run Queue)。如果LRQ满了,就加入到GRQ (Global Run Queue)
3、p1和m1绑定,m1先尝试从p1的LRQ中请求G。如果没有,就从GRQ中请求G。如果还没有,就随机从别的P的LRQ中偷(work stealing)一部分G到本地LRQ中。
4、假设m1最终拿到了g1
5、执行,让g1的代码在m1线程上运行
5.1、如果g1正常执行完了(函数调用完成了),g1和m1解绑,执行第3步的获取下一个可执行的g
5.2、如果g1中代码主动让出控制权(让出控制权函数,类似python中yeild,又不同),g1和m1解绑,将g1加入到GRQ中,执行第3步的获取下一个可执行的g
5.3、g1中进行channel、互斥锁等操作进入阻塞态(用户态的阻塞),g1和m1解绑,执行第3步的获取下一个可执行的g。如果阻塞态的g1被其他协程g唤醒后,就尝试加入到唤醒者的LRQ中,如果LRQ满了,就连同g和LRQ 中一半转移到GRQ中。
5.4、系统调用(内核态)
① 同步系统调用时,执行如下:
如果遇到了同步阻塞系统调用,g1阻塞,m1也被阻塞了,m1和p1解绑。 从休眠线程队列中获取一个空闲线程,和p1绑定,并从p1队列中获取下一个可执行的g来执行;如果休 眠队列中无空闲线程,就创建一个线程提供给p1。 如果m1阻塞结束,需要和一个空闲的p绑定,优先和原来的p1绑定。如果没有空闲的P,g1会放到GRQ 中,m1加入到休眠线程队列中。
② 异步网络IO调用时,执行如下:
网络IO代码会被Go在底层变成非阻塞IO,这样就可以使用IO多路复用了。 m1执行g1,执行过程中发生了非阻塞IO调用(读/写)时,g1和m1解绑,g1会被网络轮询器Netpoller 接手。m1再从p1的LRQ中获取下一个Goroutine g2执行。注意,m1和p1不解绑。 g1等待的IO就绪后,g1从网络轮询器移回P的LRQ(本地运行队列)或全局GRQ中,重新进入可执行状态。
就大致相当于网络轮询器Netpoller内部就是使用了IO多路复用和非阻塞IO,类似我们课件代码中的 select的循环。GO对不同操作系统MAC(kqueue)、Linux(epoll)、Windows(iocp)提供了支持。
等待组
使用参考 https://pkg.go.dev/sync#WaitGroup
父子协程
父协程结束执行,子协程不会有任何影响。当然子协程结束执行,也不会对父协程有什么影响。父子协 程没有什么特别的依赖关系,各自独立运行。 只有主协程特殊,它结束程序结束。
TCP网络编程
package main import ( "log" "net" ) func main() { laddr, err := net.ResolveTCPAddr("tcp4", "0.0.0.0:9999") // 解析地址 if err != nil { log.Panicln(err) // Panicln会打印异常,程序退出 } server, err := net.ListenTCP("tcp4", laddr) if err != nil { log.Panicln(err) } defer server.Close() // 保证一定关闭 conn, err := server.Accept() // 接收连接,分配socket if err != nil { log.Panicln(err) } defer conn.Close() // 保证一定关闭 buffer := make([]byte, 4096) // 设置缓冲区 n, err := conn.Read(buffer) // 成功返回接收了多少字节 if err != nil { log.Panicln(err) } data := buffer[:n] conn.Write(data) // 原样写回客户端 }
goroutine在底层已经采用GMP模型,且所提供包默认采用了非阻塞网络IO
package main import ( "fmt" "log" "net" "runtime" ) func rec(conn net.Conn) { defer conn.Close() buf := make([]byte, 2048) fmt.Println("新连接建立", conn.RemoteAddr()) n, err := conn.Read(buf) if err != nil { // 对客户端主动断开的错误判断 if n == 0 { log.Printf("客户端%v主动断开连接", conn.RemoteAddr()) return } log.Fatal(err) } data := buf[:n] fmt.Printf("收到客户端%v发来数据: %T %v\n", conn.RemoteAddr().String(), data, string(data)) conn.Write(data) } func main() { // 解析地址 laddr, err := net.ResolveTCPAddr("tcp4", "127.0.0.1:9999") if err != nil { log.Panic(err) } lisner, err := net.ListenTCP("tcp4", laddr) if err != nil { log.Fatal(err) } defer lisner.Close() fmt.Printf("本地socket创建成功:%v\n", laddr.String()) for { conn, err := lisner.Accept() if err != nil { log.Fatal(err) } go rec(conn) fmt.Printf("当前有%v个协程\n", runtime.NumGoroutine()) } }
package main import ( "fmt" "log" "net" "runtime" "strings" ) var html = `<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>magedu</title> </head> <body> <h1>New China will come -- Goroutine</h1> </body> </html>` var head = `HTTP/1.1 200 OK Date: Mon, 24 Oct 2022 20:04:23 GMT Content-Type: text/html Content-Length: %d Connection: keep-alive Server: New China %s` var response = fmt.Sprintf(head, len(html), html) var resData = strings.ReplaceAll(response, "/n", "/r/n") func rec(conn net.Conn) { defer conn.Close() buf := make([]byte, 2048) fmt.Println("新连接建立", conn.RemoteAddr()) n, err := conn.Read(buf) if err != nil { // 对客户端主动断开的错误判断 if n == 0 { log.Printf("客户端%v主动断开连接", conn.RemoteAddr()) return } log.Fatal(err) } data := buf[:n] fmt.Printf("收到客户端%v发来数据: %T %v\n", conn.RemoteAddr().String(), data, string(data)) conn.Write([]byte(resData)) } func main() { // 解析地址 laddr, err := net.ResolveTCPAddr("tcp4", "127.0.0.1:9999") if err != nil { log.Panic(err) } lisner, err := net.ListenTCP("tcp4", laddr) if err != nil { log.Fatal(err) } defer lisner.Close() fmt.Printf("本地socket创建成功:%v\n", laddr.String()) for { conn, err := lisner.Accept() if err != nil { log.Fatal(err) } go rec(conn) fmt.Printf("当前有%v个协程\n", runtime.NumGoroutine()) } }