简析go语言无缓冲channel

无缓冲channel兼具通信和同步特性,在并发程序中应用颇为广泛。可以通过不带有capacity参数的内置make函数创建一个可用的无缓冲channel:c := make(chan T)。

由于无缓冲channel的运行时层实现不带有缓冲区,因此对无缓冲channel的接收和发送操作是同步的,即对于同一个无缓冲channel,只有在对其进行接收操作的goroutine和对其进行发送操作的goroutine都存在的情况下,通信才能进行,否则单方面的操作会让对应的goroutine陷入阻塞状态。

对于无缓冲channel的操作时序有以下两点:

1.发送动作一定发生在接收动作完成之前

2.接收动作一定发生在发送动作完成之前

var c = make(chan int)
var s string


func f() {
	s = "hello world"
	<-c
}


func main() {
	go f()
	c <- 5
	println(s)
}

因为f中的channel接收动作发生在主goroutine对channel发送动作完成之前, f函数中s = "hello world"又发生在channel接收动作之前,因此主goroutine在channel发生操作完成后输出的变量s一定是"hello world"。

无缓冲channel的使用场景如下:

  1. 一对一通知信号:无缓冲channel常被用于两个goroutine之间一对一地传递通知信号。
import "time"

type signal struct{}

func worker() {
	println("do work...")
	time.Sleep(1 * time.Second)
}

func spawn(f func()) <-chan signal {
	c := make(chan signal)

	go func() {
		println("start a work")
		f()
		c <- signal{}
	}()
	return c
}

func main() {
	c := spawn(worker)
	<-c
	println("work done")
}

$ go run channel-case.go
start a work
do work...
work done

该例中spawn函数返回的channel被用于承载新goroutine退出的通知信号。该信号专用于通知main goroutine。main goroutine在调用spawn函数后一直阻塞在对这个通知信号的接收动作上。

2.一对多通知信号:有些时候,无缓冲channel还被用于实现一对多的信号通知机制,这样的信号通知机制常被用于协调多个goroutine一起工作。

import (
	"fmt"
	"sync"
	"time"
)

type signal struct{}

func worker(i int) {
	fmt.Printf("worker %d: start workering\n", i)
	time.Sleep(1 * time.Second)
}

func spawnGroup(f func(i int), nums int, groupSignal <-chan signal) <-chan signal {
	c := make(chan signal)
	var wg sync.WaitGroup

	for i := 0; i < nums; i++ {
		wg.Add(1)
		go func(i int) {
			<-groupSignal
			fmt.Printf("worker %d: start worke\n", i)
			f(i)
			fmt.Printf("worker %d: work done\n", i)
			wg.Done()
		}(i + 1)
	}

	go func() {
		wg.Wait()
		c <- signal{}
	}()

	return c
}

func main() {
	groupSignal := make(chan signal)
	fmt.Println("start a group of workers")
	c := spawnGroup(worker, 5, groupSignal)

	time.Sleep(5 * time.Second)
	close(groupSignal)
	<-c
	fmt.Println("a group of workers done")
}

$ go run channel-case.go
start a group of workers
worker 4: start worke
worker 4: start workering
worker 1: start worke
worker 1: start workering
worker 2: start worke
worker 2: start workering
worker 3: start worke
worker 3: start workering
worker 5: start worke
worker 5: start workering
worker 5: work done
worker 4: work done
worker 3: work done
worker 2: work done
worker 1: work done
a group of workers done

上例中main goroutine创建了5个worker goroutine,这些goroutine启动后都阻塞在名为groupSignal的无缓冲channel上,main goroutine通过close(groupSignal)向所有worker goroutine广播”开始工作“的信号,所有worker goroutine在收到groupSignal信号后一起开始工作。就像起跑线上的运动员听到裁判员的起跑信号枪声后起跑一样。

我们看到,关闭一个channel会让所有阻塞在该channel上的接收操作返回,从而实现一种一对多的广播机制。该一对多的信号通知机制还常用于通知一组worker goroutine退出。

import (
	"fmt"
	"sync"
	"time"
)

type signal struct{}

func worker(i int, quit <-chan signal) {
	fmt.Printf("worker %d: start workering\n", i)
LOOP:
	for {
		select {
		case <-quit:
			break LOOP
		default:
			time.Sleep(1 * time.Second)
		}
	}
	fmt.Printf("worker %d: work done\n", i)
}

func spawnGroup(f func(int, <-chan signal), nums int, groupSignal <-chan signal) <-chan signal {
	c := make(chan signal)
	var wg sync.WaitGroup

	for i := 0; i < nums; i++ {
		wg.Add(1)
		go func(i int) {
			fmt.Printf("worker %d: start worke\n", i)
			f(i, groupSignal)
			wg.Done()
		}(i + 1)
	}

	go func() {
		wg.Wait()
		c <- signal{}
	}()


	return c
}

func main() {
	groupSignal := make(chan signal)
	fmt.Println("start a group of workers")
	c := spawnGroup(worker, 5, groupSignal)

	time.Sleep(5 * time.Second)
	close(groupSignal)

	<-c
	fmt.Println("a group of workers done")
}

$ go run channel-case.go
start a group of workers
worker 4: start worke
worker 4: start workering
worker 5: start worke
worker 5: start workering
worker 3: start worke
worker 3: start workering
worker 2: start worke
worker 2: start workering
worker 1: start worke
worker 1: start workering
worker 3: work done
worker 5: work done
worker 1: work done
worker 2: work done
worker 4: work done
a group of workers done

Go语言基础及实战 文章被收录于专栏

Go语言学习笔记、语法知识、技术要点和个人理解及实战

全部评论

相关推荐

03-14 18:48
重庆大学 C++
点赞 评论 收藏
分享
希望被捞的猫头鹰很理智:大概率待遇低怕硕士跑路
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务