• Goroutine


    Goroutine

    • 协程最大优势在于"轻量级",可以轻松创建上百万个而不会导致系统资源衰竭,而线程和进程通常最多也不能超过1万。
    • 创建Goroutine时为其分配4k堆栈,随着程序的执行自动增长删除。创建线程时必须指定堆栈且是固定的,通常以M为单位。
    • Thread创建和销毁都会有巨大的消耗,因为要和操作系统打交道,是内核级的,通常解决的办法就是线程池。而goroutine因为是由Go runtime负责管理的,创建和销毁的消耗非常小,是用户级。
    • 线程切换时需要保存各种寄存器状态,以便恢复,goroutines切换只需要保存三个寄存器。线程切换约1000-1500纳秒,goroutine切换约200纳秒。
    • go的协程是非抢占式的,由Go runtime主动交出控制权(对于开发者而言是抢占式的)。线程在时间片用完后,由CPU中断任务强行将其调度走,这时就必须多保存很多信息。所以Goroutine的切换比线程切换更容易
    • 从进程到线程再到协程,其实是一个不断共享,不断减少切换成本的过程。

    优雅地等子协程结束

    package main
    
    import (
    	"fmt"
    	"sync"
    )
    
    var wg sync.WaitGroup
    
    func Add(a int) int {
    	defer wg.Done()
    	fmt.Printf("num: %d\n", a)
    	return a 
    }
    
    func main() {
    	for i := 0; i < 10; i++ {
    		wg.Add(1)
    		go Add(i)
    	}
    	wg.Wait()
    }
    
    // 写法二
    package main
    
    import "sync"
    
    func main() {
    	wg := sync.WaitGroup{}
    	wg.Add(10) // 加10
    	for i := 0; i < 10; i++ {
    		go func(a, b int) {
    			defer wg.Done() // 减1
    		}(i, i+1)
    	}
    	wg.Wait() // 等待
    }
    
    // 闭包情况一
    func main() {
    	wg := sync.WaitGroup{}
    	wg.Add(10) // 加10
    	for i := 0; i < 10; i++ {
    		go func(a int) {
    			defer wg.Done() // 减1
    			// do something
    			fmt.Println(a)
    		}(i)
    	}
    	wg.Wait()
    }
    // $go run main.go
    // 9
    // 8
    // 4
    // 5
    // 0
    // 2
    // 6
    // 7
    // 1
    // 3
    
    // 闭包情况二
    func main() {
    	wg := sync.WaitGroup{}
    	wg.Add(10) // 加10
    	for i := 0; i < 10; i++ {
    		go func() {
    			defer wg.Done() // 减1
    			// do something
    			fmt.Println(i)
    		}()
    	}
    	wg.Wait()
    }
    // $go run main.go
    // 10
    // 2
    // 10
    // 10
    // 10
    // 10
    // 10
    // 10
    // 3
    // 10
    
    

    捕获子协程的panic

    • 何时会发生panic:

      • 运行时错误会导致panic,比如数组越界、除0
      • 程序主动调用panic(error)
    • panic会执行什么:

      • 逆序执行当前goroutine的defer链(recover从这里介入)
      • 打印错误信息和调用堆栈
      • 调用exit(2)结束整个进程
      func Add(d int) {
      	fmt.Println(d + 10)
      }
      
      func main() {
      	a := 4
      	defer fmt.Println("1 defer")
      	b := 2
      	defer fmt.Println("2 defer")
      	c := a / b
      	// panic(errors.New("my error"))
      	defer fmt.Println("3 defer")
      	Add(c)
      }
      
      // go run main.go
      // 12
      // 3 defer
      // 2 defer
      // 1 defer
      
      func Add(d int) {
      	fmt.Println(d + 10)
      }
      
      func main() {
      	a := 4
      	defer fmt.Println("1 defer")
      	b := 2
      	defer fmt.Println("2 defer")
      	c := a / b
      	panic(errors.New("my error"))
      	defer fmt.Println("3 defer")
      	Add(c)
      }
      
      // go run main.go
      // 2 defer
      // 1 defer
      // panic: my error
      
      // goroutine 1 [running]:
      // main.main()
      //         E:/go/main.go:31 +0x109
      // exit status 2
      

    defer

    • defer在函数退出前被调用,注意不是在代码的return语句之前执行,因为return语句不是原子操作
    • 如果发生panic,则之后注册的defer不会执行
    • defer服从先进后出原则(栈),既一个函数里如果注册了多个defer,则按注册的逆序执行

    recover恢复

    不要通过共享内存的方式进行通信,而是应该通过通信的方式共享内存

    chan struct{}

    • channel仅作为协程间同步的工具,不需要传递具体的数据,管道类型可以用struct{}
    • s := make(chan struct{])
    • sc <- struct{}{}
    • 空结构体变量的内存占用为0,因此struct{}类型的管道比bool类型的管道还要省内存

    关闭channel

    • 只有当管道关闭时,才能通过range遍历管道里的数据,否则会发生fatal error
    • 管道关闭后读操作会立即返回,如果缓冲已空会返回"0值"
    • e, ok := <-ch
      • ok == true 代表管道还没有关闭

    同步channel

    • 创建同步管道 syncChann := make(chan int)
    • 往管道里放数据 syncChann <- 1
    • 从管道取出数据 v: <-syncChann 消费者
    • 没有往管道里发生数据时,取操作会阻塞或fatal error(子协程)
    • 取操作没有准备好时,往管道里发生数据会阻塞或fatal error: all goroutines are asleep - deadlock!注意不是panic,通过recover不能获取
    // 示例
    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    var ch = make(chan int)
    
    func consumer(ch chan int) {
    	for {
    		v := <-ch // 阻塞
    		fmt.Printf("取出:%d\n", v)
    	}
    }
    
    func main() {
    	go consumer(ch)
    	ch <- 5
    	time.Sleep(1 * time.Second)
    }
    
    // go run .\async_channel.go
    // 取出:5
    
    // 异常示例
    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    var ch = make(chan int)
    
    func consumer(ch chan int) {
    	for {
    		v := <-ch // 阻塞
    		fmt.Printf("取出:%d\n", v)
    	}
    }
    
    func main() {
    	defer func() { // recover无法获取异常
    		if err := recover(); err != nil {
    			fmt.Println(err)
    		}
    	}()
    	ch <- 5 // 先写管道
    	go consumer(ch) // 消费者协程
    	time.Sleep(1 * time.Second)
    }
    
    // go run .\async_channel.go
    // fatal error: all goroutines are asleep - deadlock!
    
    // goroutine 1 [chan send]:
    // main.main()
    //         E:/go/async_channel.go:18 +0x40
    // exit status 2
    
    // 生产者协程,没有消费者阻塞住,不执行下去
    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    var ch = make(chan int)
    
    func consumer(ch chan int) {
    	for {
    		v := <-ch // 阻塞
    		fmt.Printf("取出:%d\n", v)
    	}
    }
    
    func main() {
    	go func() {
    		ch <- 5 // 阻塞住,不往下执行
    		fmt.Println("发生成功")
    	}()
    	time.Sleep(1 * time.Second)
    }
    
    // go run sync_channel.go // 不打印,因为阻塞住了
    

    异步channel

    • 创建异步管道 ch:=make(chan int, 8)
    • 会创建一个环形缓冲队列,队列满时send操作会阻塞或fatal error // send在main主协程会fatal error,子协程里会阻塞

    正常示例

    package main
    
    import "fmt"
    
    var ch = make(chan int, 10)
    
    func send(ch chan int) {
    	for i := 0; i < 10; i++ {
    		ch <- i
    		fmt.Printf("send %d\n", i)
    	}
    }
    
    func main() {
    	send(ch)
    }
    
    // go run .\chan_example.go
    send 0
    send 1
    send 2
    send 3
    send 4
    send 5
    send 6
    send 7
    send 8
    send 9
    

    main主协程,send管道满了:fatal error

    package main
    
    import "fmt"
    
    var ch = make(chan int, 10)
    
    func send(ch chan int) {
    	for i := 0; i < 11; i++ {
    		ch <- i
    		fmt.Printf("send %d\n", i)
    	}
    }
    
    func main() {
    	send(ch)
    }
    
    // go run .\chan_example.go
    send 0
    send 1
    send 2
    send 3
    send 4
    send 5
    send 6
    send 7
    send 8
    send 9
    fatal error: all goroutines are asleep - deadlock!
    
    goroutine 1 [chan send]:
    main.send(0xc0000160b0)
            E:/GO_project/chan_example.go:16 +0x54
    main.main()
            E:/GO_project/chan_example.go:22 +0x34
    exit status 2
    

    子协程管道满了会阻塞

    package main
    
    import "fmt"
    
    var ch = make(chan int, 10)
    
    func send(ch chan int) {
    	for i := 0; i < 11; i++ {
    		ch <- i
    		fmt.Printf("send %d\n", i)
    	}
    }
    
    func main() {
    	go send(ch)
    	time.Sleep(1 * time.Second)
    }
    
    // go run .\chan_example.go
    send 0
    send 1
    send 2
    send 3
    send 4
    send 5
    send 6
    send 7
    send 8
    send 9 // 观察到明显阻塞
    

    main主协程,receive超出管道会fatal error

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    var ch = make(chan int, 10)
    
    func receive(ch chan int) {
    	for i := 0; i < 5; i++ {
    		v := <-ch
    		fmt.Printf("receive %d\n", v)
    	}
    }
    
    func send(ch chan int) {
    	for i := 0; i < 1; i++ {
    		ch <- i
    		fmt.Printf("send %d\n", i)
    	}
    }
    
    func main() {
    	send(ch)
    	receive(ch)
    	time.Sleep(1 * time.Second)
    }
    
    // go run .\chan_example.go
    send 0
    receive 0
    fatal error: all goroutines are asleep - deadlock!
    
    goroutine 1 [chan receive]:
    main.receive(0xc00009c000)
            E:/GO_project/chan_example.go:12 +0x58
    main.main()
            E:/GO_project/chan_example.go:26 +0x44
    exit status 2
    

    子协程管道取不出值,会阻塞

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    var ch = make(chan int, 10)
    
    func receive(ch chan int) {
    	for i := 0; i < 5; i++ {
    		v := <-ch
    		fmt.Printf("receive %d\n", v)
    	}
    }
    
    func send(ch chan int) {
    	for i := 0; i < 1; i++ {
    		ch <- i
    		fmt.Printf("send %d\n", i)
    	}
    }
    
    func main() {
    	send(ch)
    	go receive(ch)
    	time.Sleep(1 * time.Second)
    }
    
    // go run .\chan_example.go
    send 0
    receive 0
    

    使用缓冲示例

    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    var ch = make(chan int, 10)
    
    func receive(ch chan int) {
    	for i := 0; i < 5; i++ {
    		v := <-ch
    		fmt.Printf("receive %d\n", v)
    	}
    }
    
    func send(ch chan int) {
    	for i := 0; i < 15; i++ { // 超出了缓冲管道范围
    		ch <- i
    		fmt.Printf("send %d\n", i)
    	}
    }
    
    func main() {
    	go send(ch)
    	go receive(ch)
    	time.Sleep(1 * time.Second)
    }
    
    // go run .\chan_example.go
    send 0
    send 1
    send 2
    send 3
    send 4
    send 5
    send 6
    send 7
    send 8
    send 9
    send 10
    receive 0
    receive 1
    receive 2
    receive 3
    receive 4
    send 11
    send 12
    send 13
    send 14
    

    管道先取值的话

    func main() {
    	go receive(ch)
    	go send(ch)
    	time.Sleep(1 * time.Second)
    }
    
    // go run .\chan_example.go
    send 0
    send 1
    send 2
    send 3
    send 4
    send 5
    send 6
    receive 0
    receive 1
    receive 2
    receive 3
    receive 4
    send 7
    send 8
    send 9
    send 10
    send 11
    send 12
    send 13
    send 14
    

    子协程套子协程(孙协程)

    • 子协程销毁,主协程还在运行,“孙”协程依旧可以运行至结束
    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func grandson() {
    	fmt.Println("grnadson channel")
    	time.Sleep(3 * time.Second)
    	fmt.Println("grandson end")
    }
    
    func son() {
    	fmt.Println("son channel")
    	go grandson()
    	time.Sleep(1 * time.Second)
    	fmt.Println("son end")
    }
    
    func main(){
    	fmt.Println("main channel")
    	go son()
    	time.Sleep(4 * time.Second)
    	fmt.Println("main end")
    }
    
    // go run grandson_channel.go
    main channel
    son channel
    grnadson channel
    son end
    grandson end
    main end
    
    • 主协程销毁,所有子/孙协程都会提前退出
    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func grandson() {
    	fmt.Println("grnadson channel")
    	time.Sleep(3 * time.Second)
    	fmt.Println("grandson end")
    }
    
    func son() {
    	fmt.Println("son channel")
    	go grandson()
    	time.Sleep(1 * time.Second)
    	fmt.Println("son end")
    }
    
    func main(){
    	fmt.Println("main channel")
    	go son()
    	time.Sleep(2 * time.Second)
    	fmt.Println("main end")
    }
    
    // go run .\grandson_channel.go
    main channel
    son channel
    grnadson channel
    son end
    main end
    
    • 主、子、孙运行时间相同,概率性同时结束或子孙提前退出
    package main
    
    import (
    	"fmt"
    	"time"
    )
    
    func grandson() {
    	fmt.Println("grnadson channel")
    	time.Sleep(1 * time.Second)
    	fmt.Println("grandson end")
    }
    
    func son() {
    	fmt.Println("son channel")
    	go grandson()
    	time.Sleep(1 * time.Second)
    	fmt.Println("son end")
    }
    
    func main(){
    	fmt.Println("main channel")
    	go son()
    	time.Sleep(1 * time.Second)
    	fmt.Println("main end")
    }
    

    GMP模型

    • G(Goroutine)本质是一种轻量级的线程。蓝色是正在执行的Goroutine,灰色在等待队列中。
    • M(Machine)对应一个内核线程
    • P(Processor)虚拟处理器,代表M所需的上下文环境,是处理用户级代码逻辑的处理器。P的数量由环境变量中的GOMAXPROCS决定,默认情况下就是核数。
    • GMP详情
      • M和内核线程的对应关系是确定的
      • M进入系统调用时,会抛弃P,P被挂在其它M上,然后继续执行G队列
      • 系统调用返回后,相应的G进入全局的可运行队列(runqueue)中,P会周期性扫描这个全局的runqueue,使上面的G得到执行
  • 相关阅读:
    RecyclerView与各种异步图片加载框架不兼容的问题
    课内上机实验3——括号匹配(栈)
    课内上机实验3——删除重复元素
    课内上机实验3——数组内移动0元素至末尾
    课内上机实验3——M集合问题(队列)
    递归实践1——Cnm组合数计算
    【转】Quine的编写
    【转】fork函数详解
    【转】Makefile详解
    VC++6.0程序安装
  • 原文地址:https://www.cnblogs.com/Otiger/p/16221778.html
Copyright © 2020-2023  润新知