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) // 原样写回客户端
}
TCP编程

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())
    }
}
html数据返回

 

posted on 2023-08-15 15:24  自然洒脱  阅读(62)  评论(0编辑  收藏  举报