Golang 并发编程实战——协程、管道、select用法(CV大使) | 青训营笔记

107 阅读3分钟
术语解析
goroutine协程,比线程轻量级
channel管道,用于多个goroutine之间的通信

管道的用法

func boring(msg string, c chan string) {
	for i := 0; ; i++ {
		// 发送信息给管道 (hannel / chan)
		// 同时,它也在等待管道的消费者消费完成
		c <- fmt.Sprintf("%s %d", msg, i)
		time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)
	}
}

func main() {
	c := make(chan string) // 初始化一个管道
	go boring("boring!", c)

	for i := 0; i < 5; i++ {
		// `<-c` 等待 `boring` 方法给它发送值,如果一直没有收到,那么会被阻塞在这一步
		fmt.Printf("You say: %q\n", <-c)
	}
	fmt.Println("You're boring. I'm leaving")
}

上述方法简单说就是boring方法在给管道c发送数据,并且等待另一头,也就是main方法来消费。由于管道中只能够存在一个数据,所以main方法和boring方法在某些程度上是交替运行的。但实际上不完全是,以main方法来说,接受到管道的数据后可以直接进行下一步,而不需要继续等待。

在Go语言中,通道是goroutine与另一个goroutine通信的媒介,并且这种通信是无锁的。换句话说,通道是一种允许一个goroutine将数据发送到另一个goroutine的技术。默认情况下,通道是双向的,这意味着goroutine可以通过同一通道发送或接收数据

协程间通信

type Message struct {
	str  string    // 真正要传输的数据
	wait chan bool // 
}

func fanIn(inputs ...<-chan Message) <-chan Message {
	c := make(chan Message)
	for i := range inputs {
		input := inputs[i]
		go func() {
			for {
				c <- <-input
			}
		}()
	}
	return c
}

// `boring` 是一个返回管道的方法,该管道用于和 `boring` 方法通信
func boring(msg string) <-chan Message {
	c := make(chan Message)
	waitForIt := make(chan bool)
	go func() {
		for i := 0; ; i++ {
			c <- Message{
				str:  fmt.Sprintf("%s %d", msg, i),
				wait: waitForIt, // 将管道注入到返回值中,用于协程之间的通信
			}
			time.Sleep(time.Duration(rand.Intn(1e3)) * time.Millisecond)

			// 协程需要在这里等到接收到信息才能够继续执行后面的逻辑
			<-waitForIt
		}

	}()
	return c
}

func main() {
	// merge 2 channels into 1 channel
	c := fanIn(boring("Joe"), boring("Ahn"))

	for i := 0; i < 5; i++ {
		msg1 := <-c // 等到从管道中接受数据
		fmt.Println(msg1.str)
		msg2 := <-c
		fmt.Println(msg2.str)

		// 由于 boring 协程中需要等待 wait 信号才能继续执行,所以这一步能够保证两个协程都能够输出一次数据
		msg1.wait <- true // main 协程允许 boring 协程继续执行任务
		msg2.wait <- true // main 协程允许 boring 协程继续执行任务
	}
	fmt.Println("You're both boring. I'm leaving")
}

select 是 Go 中的一个控制结构,类似于用于通信的 switch 语句。每个 case 必须是一个通信操作,要么是发送要么是接收。 select 随机执行一个可运行的 case。如果没有 case 可运行,那么会执行 default 里的操作,如果没有 default,那么它将阻塞,直到有 case 可运行。一个默认的子句应该总是可运行的。

Web页面中,搜索是一个很常见的功能,多数情况下,我们会使用一个微服务来搭建一个搜索服务,如ElasticSearch就是一个单独的服务。在这里,我们不会真的模拟一个ES来处理,反之,我们用一个随机延时的函数来代替它。由于搜索的时间不能够保证,有时候会很快,但有时候也会慢,不管是因为搜索本身就需要时间还是由于IO的耗时。 在这个案例中,我们将会循序渐进来告诉你如何更好的利用goroutinechan来处理这个问题。除此以外,这里还使用了函数式编程技巧,如果你对这个还不太熟悉,可以先了解一些相关的知识再来继续阅读。