goroutine、chan应用

55 阅读3分钟

goroutine

并发

  • goroutine并非执行并发操作,而是创建一个并发任务单元。新建任务被放置在系统队列中,等待调度器安排合适系统线程去获取执行。当前流程不会阻塞,不会等待任务启动,且运行不保证并发执行次序。
  • 每个任务单元除了保存函数指针、调用参数外、还会分配所需栈空间
  • 系统线程默认MB,goroutine自定义栈初始化仅2KB,才能创建成千上万并发任务

GMP

  • G:goroutine用户态协程
  • M:操作系统线程
  • P:调度器,与M一一对应,调度goroutine

chan

  • 底层实现,通道是一个队列
  • chan不用用共享内存通信,让通信共享内存
  • 允许全局变量、指针、引用类型非安全内存共享操作,需自行维护数据一致性和完整性
  • 同步模式,无缓存chan,发送和接收方配对后传送数据,接收方先准备好,否则死锁
  • 异步模式,有缓存chan,在缓冲区未满或数据未读完前,不会阻塞

对于close或nil通道,发送和接收操作相应规则

  • 向已关闭通道发送数据,引发panic
  • 从已关闭接收数据,返回已缓冲数据或零值
  • 无论收发,nil通道都会阻塞

goroutine应用

参数是立即复制执行

func goArgs() {
	a := 100

	go func(x, y int) {
		time.Sleep(time.Second)
		fmt.Println("go:", x, y) // go: 100, 1
	}(a, counter())

	a += 100
	fmt.Println("main:", a, counter()) // main: 200, 2
	time.Sleep(3 * time.Second)
}

单个并发,可用chan,阻塞控制

func goChanExit() {
	exit := make(chan struct{})

	go func() {
		time.Sleep(time.Second)
		fmt.Println("goroutine done") // 2 goroutine done
		close(exit)                   // 关闭通道发出信号
	}()

	fmt.Println("main...") // 1 main...
	<-exit
	fmt.Println("main exit") // 3 main exit
}

多个并发,可用sync.WaitGroup

func goWaitGroup() {
	// GMP中M线程默认与处理器核数相等
	n := runtime.GOMAXPROCS(4) // 修改运行线程数量
	var wg sync.WaitGroup

	for i := 0; i < n; i++ {
		wg.Add(1)   // 在gorutine外增加计数器
                
		go func(id int) {
			defer wg.Done()
			time.Sleep(time.Second)
			fmt.Println("goroutine", id, "done")  // 无序输出
		}(i)
	}

	fmt.Println("main...")
	wg.Wait()
	fmt.Println("main exit.")
}

chan应用

同步模式,无缓冲chan,发送数据或信号

func chanNotify() {
	done := make(chan struct{})  // 信号
	c := make(chan string)

	go func() {
		s := <-c // 接收数据
		fmt.Println(s)
		close(done) // 关闭
	}()

	c <- "hi"  // 发送数据
	<-done  // 阻塞,等待关闭
}

异步模式,有缓存chan

func channelBuffer() { 
	var c chan int  // chan的零值为nil
	fmt.Println(c == nil) // true 

	ping := make(chan string, 2) // chan为指针
	pong := make(chan string, 2)
	fmt.Println(ping == pong) // false

	fmt.Printf("%p,%d\n", pong, unsafe.Sizeof(pong)) // 0xc000056180,8

	ping <- "buffered"
	ping <- "channel" 
	// ping <- "hi"  缓存区满阻塞 deadlock

	// len返回当前已缓存数量,cap返回缓冲区大小
	fmt.Println("a:", len(c), cap(c)) // a: 0,0 
	fmt.Println("ping:", len(ping), cap(ping)) // ping: 2,2

	fmt.Println(<-ping) // buffered
	fmt.Println(<-ping) // channel
}

判断chan存在及返回值

func chanOkIdom() {
	done := make(chan struct{})
	c := make(chan int)

	go func() {
		defer close(done)
		for {
			s, ok := <-c // 判断通道是否关闭
			if !ok {  // 不存在s为类型零值
				return
			}
			fmt.Println(s)
		}
	}()

	c <- 1
	c <- 2
	c <- 3
	close(c)
	<-done
}

遍历chan

func chanRange() {
	done := make(chan struct{})
	c := make(chan int)

	go func() {
		defer close(done)
		for s := range c { // 循环获取消息,直到通道关闭,for循环退出
			fmt.Println(s)
		}
	}()

	c <- 1
	c <- 2
	c <- 3
	close(c)
	<-done
}

多个chan用select,随机选择一个通道操作

func chanSelect() {
	tick := time.Tick(10 * time.Millisecond)
	boom := time.After(4 * time.Millisecond)
	// default
	// BOOM!
	for {
		select {
		case <-tick:
			fmt.Println("tick.")
		case <-boom:
			fmt.Println("BOOM!")
			// break只能跳出select,无法跳出for,用return
			// 跳出select后面都不执行,退出函数
			return
		default:
			fmt.Println("default")
			time.Sleep(6 * time.Millisecond) // 避免打印大量default
		}
	}
}