Go 语言入门指南-信道 | 青训营

78 阅读3分钟

在 go 中并发只需要用 goruntime 的轻量级线程就行,但是 go 首先是不喜欢直接操作共享内存,而是推荐你通过信道的方式对不同进程进行交互。

同时主进程的结束会导致子进程的异常,比如信道接受方消失,或者输出未被执行

信道

信道只能接受声明时声明的数据类型

信道分为带缓冲和不带缓冲两种,两种方式只在声明时有区别,例如

	ch1 := make(chan string) //无缓冲信道
	ch2 := make(chan string, 1) //有缓冲信道

读取信道时可以通过第二个变量来判断是否信道被关闭

	val, ok := <- ch1

同时信道可以是只读、只写的

//定义只读的channel
read_only := make (<-chan int)
//定义只写的channel
write_only := make (chan<- int)

不带缓冲的信道

不带缓冲的信道只有输出和输入同时准备好才能进行使用,也就是不能在一个程序中同时输出和输入

	ch1 := make(chan string)
	ch1 <- "1"
	fmt.Println(<-ch1)

以上这段代码会导致死锁。

只有当我们开启一个新进程时,在两端同时准备好才能进行使用

	ch1 := make(chan string)
	go func () {
		ch1 <- "1"
	}()
	fmt.Println(<-ch1)

可以同时可以由多个进程进行存入,由多个进行取出

	ch1 := make(chan string)
	for i := 0; i < 10; i++ {
		go func() {
			ch1 <- "1"
		}()
		go func() {
			ch1 <- "2"
		}()
	}
	for i := 0; i < 10; i++ {

		fmt.Println(<-ch1)
		fmt.Println(<-ch1)
	}

带缓冲的信道

带缓冲的信道的缓冲就想PV原语中mutex,通过信道操作符进行类似PV的操作

这是带缓冲的信道

func main() {
	ch1 := make(chan string, 2)

	go func() {
		ch1 <- "1"
	}()
	go func() {
		ch1 <- "2"
	}()
	fmt.Println(<-ch1)
	fmt.Println(<-ch1)
}

缓冲区空时接受方阻塞,缓冲区满时输出方阻塞

举个例子,有两台打印机,打印机可以装纸也可以打印,打印机能容纳2张纸,打印消耗打印机里的纸,装纸就增加打印机里的纸,没纸打印不出来会导致阻塞,没地方放纸会导致阻塞。

这个情景下,打印机是信道,容量是缓存大小,打印是输出,装纸是输入,导致阻塞会错误

range 和 close

go 可以通过 range 不断取出信道的中的内容,直到信道被关闭

	for i := range ch1 {
		fmt.Println("i:", i)
	}

虽然直接使用 range 取出十分方便,但是由于不关闭信道会导致 range 出现错误,并且由于有些情况下比较难以进行关闭,比如递归,所以使用 range 直接读取可能会比较麻烦

close(ch1)

通过这样关闭信道,主要是告诉接收者没有需要发送的值,例如终止一个 range 循环。但是 close 以后不能再写入,重复 close 会出现 panic ,只读的信道不能 close,以及 close 以后还可以读取数据

selcet 和默认选择

select 使一个进程可以等待多个通信操作。

而当 select 中的其它分支都没有准备好时,default 分支就会执行。

select {
case i := <-ch1:
    //对 i 进行操作
case j := <-ch2:
    //对 j 进行操作
default:
    // 从 ch1, ch2 中接收都阻塞时执行
}

总结

本文基于 gotour 的并发一部分并按照我自己的实际感觉进行修改和调整,个人感觉 go 的并发使用简单,但是由于有些原生操作不支持并发,导致实际使用时需要借用一些锁