用无缓冲通道等待线程结束

在线运行open in new window启动 AI 助手open in new window

func main() {
	ch := make(chan struct{})

	go func() {
		fmt.Println("go routine")
		ch <- struct{}{}
	}()

	<-ch

	fmt.Println("safe to exit")
}

在程序中,首先使用 make() 函数创建了一个无缓冲的通道 ch。然后创建了一个匿名的协程,协程中输出了一条消息,并将一个空结构体类型的值写入到通道 ch 中。空结构体类型的值在 Go 编程语言中被称为“空信号”,用于表示一个不包含任何信息的信号。

接着,程序使用 <-ch 语句从通道 ch 中读取一个值。由于通道是无缓冲的,因此该操作会被阻塞,直到协程中的代码将一个空结构体类型的值写入到通道 ch 中。

最后,程序输出了一条消息,表示可以安全地退出了。由于协程中的代码已经执行完毕,并将一个空结构体类型的值写入到通道 ch 中,因此通道 ch 中已经包含了需要的信号,程序可以安全地退出。

上面示例交换读写顺序没影响

在线运行open in new window启动 AI 助手open in new window

func main() {
	ch := make(chan struct{})

	go func() {
		fmt.Println("go routine")
		<-ch
	}()

	ch <- struct{}{}

	fmt.Println("safe to exit")
}

在程序中,首先使用 make() 函数创建了一个无缓冲的通道 ch。然后创建了一个匿名的协程,协程中输出了一条消息,并使用 <-ch 语句从通道 ch 中读取一个值。由于通道是无缓冲的,因此该操作会被阻塞,直到主协程中的代码将一个空结构体类型的值写入到通道 ch 中。

接着,主协程中的代码使用 ch <- struct{}{} 语句将一个空结构体类型的值写入到通道 ch 中。由于通道是无缓冲的,因此该操作会被阻塞,直到协程中的代码将一个值从通道 ch 中读取出来。这个过程会使得协程中的代码继续执行。

最后,程序输出了一条消息,表示可以安全地退出了。由于协程中的代码已经执行完毕,并从通道 ch 中读取了一个值,程序可以安全地退出。需要注意的是,在这个过程中,主协程和子协程之间的执行顺序是不确定的,因此可能会有不同的输出结果。

通道作为同步读写的数据容器

在线运行open in new window启动 AI 助手open in new window

func main() {
	ch := make(chan int)

	go func() {
		for i := 0; i < 10; i++ {
			ch <- i

			time.Sleep(time.Second)
		}

		// 不关闭通道会 deadlock
		close(ch)
	}()

	// ch 没数据会阻塞,知道 ch 被 close 掉后结束
	for data := range ch {
		fmt.Println(data)
	}
}

在程序中,首先使用 make() 函数创建了一个无缓冲的通道 ch。然后创建了一个匿名的协程,协程中循环 10 次,每次将一个整数值 i 写入到通道 ch 中,并使用 time.Sleep() 函数使协程休眠 1 秒钟。最后,协程中调用了 close() 函数关闭通道 ch。

接着,在主协程中使用 for-range 循环来从通道 ch 中读取数据。由于通道是无缓冲的,因此这个操作会被阻塞,直到协程中的代码将一个值写入到通道 ch 中。在协程中,由于每次写入值之后都休眠了 1 秒钟,因此在主协程中读取值的过程中也会出现 1 秒钟的延迟。当协程中的代码执行完毕后,会调用 close() 函数关闭通道 ch,这样在主协程中的 for-range 循环中就可以检测到通道已经关闭了,并且退出循环。

需要注意的是,在使用无缓冲通道实现生产者-消费者模型时,如果不调用 close() 函数关闭通道,程序可能会因为协程一直阻塞在向通道中写入数据的操作而导致死锁。因此,在生产者不再向通道中写入数据时,应该调用 close() 函数来关闭通道。

上面的例子改为有缓冲通道也行

因为通道写满或者没有数据会阻塞。

在线运行open in new window启动 AI 助手open in new window

func worker(ch chan struct{}) {
	fmt.Println("go routine")
	ch <- struct{}{}
}

func main() {
	ch := make(chan struct{}, 2)

	go worker(ch)
	<-ch

	fmt.Println("safe to exit")
}

在程序中,首先使用 make() 函数创建了一个缓冲大小为 2 的通道 ch。然后创建了一个名为 worker 的函数,函数中输出一条消息,并使用 ch <- struct{}{} 语句向通道 ch 中写入一个空结构体类型的值。

接着,在主函数中创建了一个匿名的协程,协程中调用了 worker 函数,并使用 <-ch 语句从通道 ch 中读取一个值。由于通道是有缓冲的,因此 ch <- struct{}{} 语句不会被阻塞,而是会将一个空结构体类型的值写入到通道 ch 中,使得协程中的代码继续执行。然后,主协程使用 <-ch 语句从通道 ch 中读取一个值,这个操作会被阻塞,直到协程中的代码将一个值写入到通道 ch 中。

最后,程序输出了一条消息,表示可以安全地退出了。由于协程中的代码已经执行完毕,并将一个空结构体类型的值写入到通道 ch 中,因此通道 ch 中已经包含了需要的信号,程序可以安全地退出。需要注意的是,由于通道 ch 的缓冲大小为 2,因此可以保证程序在任何情况下都不会被阻塞。

使用 chan 并发累计计数

在线运行open in new window启动 AI 助手open in new window

func main() {
	queue := make(chan int, 10)
	str := "I came out on the chariot of the first gleam of light, and pursued my voyage through the wildernesses of worlds leaving my track on many a star and planet."
	words := strings.Split(str, " ")

	wg := &sync.WaitGroup{}
	for _, word := range words {
		wg.Add(1)

		go func(w string) {
			defer wg.Done()
			queue <- len(w)
		}(word)
	}

	total := 0
	exit := make(chan struct{})

	go func() {
		for cnt := range queue {
			total += cnt
		}

		<-exit
	}()

	wg.Wait()
	close(queue)
	exit <- struct{}{}

	fmt.Println(total) // 126
}

程序功能是计算一个字符串中所有单词的长度之和。程序使用了 goroutine 和 channel 实现并发处理。

在程序中,首先使用 make() 函数创建了一个缓冲大小为 10 的通道 queue,用于存储单词的长度。然后使用 strings.Split() 函数将字符串 str 分割成多个单词,并遍历这些单词。对于每个单词,程序创建一个匿名的 goroutine,并向其中传递单词本身。在 goroutine 中,程序调用了 len() 函数获取单词的长度,并将这个长度值写入到通道 queue 中。

接着,程序创建了一个整型变量 total,用于保存所有单词的长度之和。同时,程序使用 make() 函数创建了一个无缓冲的通道 exit,用于通知计算协程已经执行完毕。

在主函数中,程序创建了一个匿名的 goroutine,用于计算单词长度的总和。这个 goroutine 不断地从通道 queue 中读取数据,直到通道被关闭。每当从通道中读取到一个长度值时,程序将其累加到变量 total 中。当通道被关闭后,程序从通道 exit 中读取一个值,使得 goroutine 可以安全地退出。

最后,程序调用了 wg.Wait() 等待所有的 goroutine 执行完毕。然后,程序调用了 close(queue) 函数关闭通道 queue,并向通道 exit 中写入一个空结构体类型的值,通知计算协程可以安全地退出了。最后,程序输出了变量 total 的值,表示所有单词的长度之和。

Last Updated:
Contributors: Bob Wang