ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

Goroutine

2022-05-04 18:02:00  阅读:118  来源: 互联网

标签:fmt Goroutine Println func time go main


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得到执行

标签:fmt,Goroutine,Println,func,time,go,main
来源: https://www.cnblogs.com/Otiger/p/16221778.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有