Bootstrap

go语言的成神之路-筑基篇-管道

目录

一、初识管道

二、无缓冲管道

 代码解析:

注意事项:

三、有缓冲管道

 代码解析:

注意事项:

四、close函数关闭管道

示例:

代码解析: 

注意事项:

检查通道是否关闭 

 代码解析:

五、通道循环取值

代码解析

注意事项 

六、单向通道

代码解析:

注意事项:


一、初识管道

channel 是 Go 语言中用于在 goroutine 之间进行通信和同步的一种机制。它可以安全地在不同的 goroutine 之间传递数据,并且可以用来实现同步操作。

channel 可以是无缓冲的(unbuffered)或有缓冲的(buffered)。

无缓冲的 channel:发送操作会阻塞,直到另一个 goroutine 执行接收操作,反之亦然。

有缓冲的 channel:可以存储一定数量的数据,发送操作会在缓冲区未满时立即完成,接收操作会在缓冲区不为空时立即完成。

二、无缓冲管道

package main

import (
	"fmt"
	"time"
)

func main() {
	// 创建一个无缓冲的整数通道
	ch := make(chan int)
	// 启动一个匿名 goroutine,它将在一秒钟后向通道发送数据 10
	go func() {
		time.Sleep(time.Second)
		ch <- 10
	}()
	// 从通道接收数据,此操作会阻塞,直到有数据发送到通道
	num := <-ch
	// 打印接收到的数据
	fmt.Println(num)
}

 代码解析:

  • ch := make(chan int):创建一个无缓冲的整数通道,用于在不同的 goroutine 之间传递数据。
  • go func() {...}:启动一个匿名 goroutine,在其中使用 time.Sleep(time.Second) 让该 goroutine 暂停一秒钟,然后将整数 10 发送到通道 ch 中。
  • num := <-ch:从通道 ch 中接收数据,由于通道是无缓冲的,此操作会阻塞,直到有数据发送到通道中。
  • fmt.Println(num):将接收到的数据打印输出。

注意事项

  • 由于使用了 time.Sleep 来等待 goroutine 发送数据,这不是一种可靠的同步方式,在实际应用中可以使用 sync.WaitGroup 或其他同步机制来确保 goroutine 完成任务。
  • 无缓冲通道的发送和接收操作会相互阻塞,直到另一方准备好,这保证了数据的同步传递。

三、有缓冲管道

package main

import (
	"fmt"
	"time"
)

func main() {
	// 创建一个有缓冲的通道,容量为 20
	ch := make(chan int, 20)

	// 向通道发送数据
	ch <- 1
	ch <- 2
	ch <- 3
	ch <- 4

	// 启动一个 goroutine 向通道发送数据
	go func() {
		time.Sleep(1 * time.Second)
		ch <- 3
	}()

	// 接收数据
	fmt.Println(<-ch)
	fmt.Println(<-ch)
	fmt.Println(<-ch)
	fmt.Println(<-ch)
	fmt.Println(<-ch)

}

 代码解析:

  • func main() {...}:这是 Go 程序的主函数,程序从这里开始执行。
  • ch := make(chan int, 20):创建一个有缓冲的通道 ch,其容量为 20,可存储 20 个整数。
  • ch <- 1ch <- 2ch <- 3ch <- 4:向通道 ch 发送四个整数 1、2、3 和 4。由于通道是有缓冲的且缓冲区未满,这些发送操作不会阻塞。
  • go func() {...}:启动一个匿名 goroutine,在其中使用 time.Sleep(1 * time.Second) 让该 goroutine 暂停一秒钟,然后将整数 3 发送到通道 ch 中。
  • fmt.Println(<-ch):从通道 ch 中接收数据并打印。这里有五次接收操作,前四次接收的是之前发送的 1、2、3、4 中的数据,最后一次接收的是匿名 goroutine 发送的数据 3(如果它已经发送)。由于通道是有缓冲的,接收操作不会阻塞,除非缓冲区为空。

注意事项:

  • 有缓冲通道在缓冲区未满时发送操作不会阻塞,在缓冲区不为空时接收操作不会阻塞。

四、close函数关闭管道

在 Go 语言中,close 函数用于关闭通道(channel)。关闭通道是一个重要的操作,它可以向接收方发送一个信号,表示不会再有数据发送到该通道。

示例:

package main

import (
	"fmt"
)

func main() {
	ch := make(chan int, 3)
	ch <- 1
	ch <- 2
	ch <- 3
	close(ch)

	// 接收通道中的数据
	for num := range ch {
		fmt.Println(num)
	}
}

代码解析: 

  • ch := make(chan int, 3):创建一个有缓冲的通道,容量为 3。
  • ch <- 1ch <- 2ch <- 3:向通道发送数据。
  • close(ch):关闭通道。
  • for num := range ch {...}:使用 range 从通道中接收数据。当通道关闭时,range 循环会自动结束,不会阻塞。

注意事项

  • 关闭一个已经关闭的通道会导致 panic,所以在调用 close 函数之前,需要确保通道没有被关闭过。
  • 向一个已经关闭的通道发送数据也会导致 panic。
  • 可以使用 v, ok := <-ch 来检查通道是否关闭,其中 ok 为 false 表示通道已关闭。

检查通道是否关闭 

package main

import (
	"fmt"
)

func main() {
	ch := make(chan int, 3)
	ch <- 1
	ch <- 2
	ch <- 3
	close(ch)

	for {
		val, ok := <-ch
		if!ok {
			fmt.Println("Channel is closed")
			break
		}
		fmt.Println(val)
	}
}

 代码解析:

  • v, ok := <-ch:从通道接收数据,ok 为 true 表示接收成功,ok 为 false 表示通道已关闭。
  • if!ok {...}:检查通道是否关闭,如果关闭则打印消息并退出循环。

使用 close 函数的场景

  • 当发送方完成数据发送后,可以关闭通道,通知接收方数据发送完毕。
  • 在 select 语句中,关闭通道可以触发相应的 case 分支。

五、通道循环取值

package main

import "fmt"

func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	go func() {
		for i := 0; i < 100; i++ {
			ch1 <- i
		}
		close(ch1)
	}()
	go func() {
		for {
			val, ok := <-ch1
			if !ok {
				break
			}
			ch2 <- val * val
		}
		close(ch2)
	}()

	for i := range ch2 {
		fmt.Println(i)
	}
}

代码解析

  • func main() {...}:这是 Go 程序的主函数,程序从这里开始执行。
  • ch1 := make(chan int):创建一个无缓冲的整数通道 ch1,用于在不同的 goroutine 之间传递数据。
  • ch2 := make(chan int):创建另一个无缓冲的整数通道 ch2,用于在不同的 goroutine 之间传递数据。
  • go func() {...}:启动第一个匿名 goroutine,在这个 goroutine 中,使用 for 循环将 0 到 99 的整数依次发送到 ch1 通道中,发送完成后关闭 ch1 通道。
  • go func() {...}:启动第二个匿名 goroutine,在这个 goroutine 中,使用 for 循环从 ch1 通道接收数据。
    • val, ok := <-ch1:从 ch1 通道接收数据,val 是接收到的数据,ok 表示通道是否关闭。
    • if!ok {...}:如果 ok 为 false,表示 ch1 通道已关闭,此时跳出循环。
    • ch2 <- val * val:将接收到的数据的平方发送到 ch2 通道中。
    • close(ch2):关闭 ch2 通道。
  • for i := range ch2 {...}:使用 for 循环从 ch2 通道接收数据,将接收到的数据打印输出。当 ch2 通道关闭时,循环自动结束。

注意事项 

  • 这里使用 close 函数关闭通道,确保接收方知道数据发送完毕,避免死锁。
  • 第一个 goroutine 发送数据,第二个 goroutine 接收并处理数据,最后在主 goroutine 中打印处理后的数据,体现了通道在不同 goroutine 间的通信和数据处理流程。

六、单向通道

package main

import "fmt"

func connter(out chan<- int) {
	for i := 0; i < 100; i++ {
		out <- i
	}
	close(out)
}
func square(out chan<- int, in <-chan int) {
	for i := range in {
		out <- i * i
	}
	close(out)
}
func main() {
	ch1 := make(chan int)
	ch2 := make(chan int)
	go connter(ch1)
	go square(ch2, ch1)
	for i := range ch2 {
		fmt.Println(i)
	}
}

代码解析:

func connter(out chan<- int) {...}

  • 这是一个名为 connter 的函数,接收一个只写通道 outchan<- int)作为参数。
  • for i := 0; i < 100; i++ {...}:使用 for 循环从 0 到 99 迭代。
  • out <- i:将迭代的数字 i 发送到 out 通道中。
  • close(out):当循环结束后,关闭 out 通道,向接收方发送信号表示数据发送完毕。

func square(out chan<- int, in <-chan int) {...}

  • 这是一个名为 square 的函数,接收两个通道作为参数:
    • out chan<- int:只写通道,用于发送数据。
    • in <-chan int:只读通道,用于接收数据。
  • for i := range in {...}:使用 range 从 in 通道接收数据,当 in 通道关闭时,循环结束。
  • out <- i * i:将接收到的数据 i 进行平方操作,并将结果发送到 out 通道。
  • close(out):当 in 通道的数据全部接收并处理完后,关闭 out 通道。

func main() {...}

  • ch1 := make(chan int):创建一个无缓冲的整数通道 ch1
  • ch2 := make(chan int):创建另一个无缓冲的整数通道 ch2
  • go connter(ch1):启动一个 goroutine 并调用 connter 函数,将 ch1 作为参数传递给它,connter 函数会向 ch1 发送 0 到 99 的数字。
  • go square(ch2, ch1):启动另一个 goroutine 并调用 square 函数,将 ch2 作为输出通道,ch1 作为输入通道,square 函数会从 ch1 接收数据,将其平方后发送到 ch2
  • for i := range ch2 {...}:使用 range 从 ch2 接收数据,并使用 fmt.Println 打印接收到的数据,当 ch2 关闭时,循环结束。

注意事项:

  • 通道的使用确保了 goroutine 之间的安全通信和同步。
  • close 函数的使用表示数据发送完毕,避免接收方无限等待。
  • 代码中没有对可能的通道操作错误(如发送到已关闭的通道或从已关闭的通道接收)进行处理,在更复杂的应用中可能需要添加错误处理机制。
  • 这里使用 range 从通道接收数据,当通道关闭时,range 循环会自动结束,这是一种简洁的处理方式。
;