跳至主要內容

Channel

王泽权大约 8 分钟GoGo

本周我们来看 Go 语言中的 channel,作为 Go 语言核心的数据结构,也是作为 goroutine 之间的通信方式,下面我们将通过一些测试代码来开始本周的 channel 的学习。

Overview

从 channel 关键字上来看大致意思为“管道”,如图 1 所示作为 Go 语言中 Goroutine 之间的通信方式。


Statement

channel 共有两种形式,分别是 Unbuffered channels 与 Buffered channels,在此之前我们需要先知道,这两种形式是如何声明的。


Unbuffered channels

ch := make(chan int)

package main

import (
	"fmt"
	"reflect"
)

func main() {
	ch := make(chan int)
	fmt.Println(len(ch), cap(ch))
	fmt.Println(reflect.TypeOf(ch))
}

// 0 0
// chan int

Buffered channels

ch := make(chan int, 2)

package main

import (
	"fmt"
	"reflect"
)

func main() {
	ch := make(chan int, 2)
	fmt.Println(len(ch), cap(ch))
	fmt.Println(reflect.TypeOf(ch))
}

// 0 2
// chan int

值得注意的是在 code - 5 中,我们发现 Go 语言中 channel 类型并不像 array 类型一样其容量也算在类型之中。

package main

import (
	"fmt"
	"reflect"
)

func main() {
	buffered := make(chan int, 4)
	unbuffered := make(chan int)

	var ch chan int = buffered
	fmt.Println(len(ch), cap(ch))
	fmt.Println(reflect.TypeOf(ch))

	ch = unbuffered
	fmt.Println(len(ch), cap(ch))
	fmt.Println(reflect.TypeOf(ch))
}

// 0 4
// chan int
// 0 0
// chan int

Testing

接下来,我们将采取测试代码的形式一步一步的分析 Go 语言中 Channel 的特性。

Communication

从当前的测试代码 code- 6 ~ 7 中我们可以发现,无缓存 channel 在发送消息后,会堵塞当前的线程,导致之后的程序无法运行。

package main

import (
	"fmt"
)

func main() {
	ch := make(chan string)
	message := "hello"

	ch <- message

	fmt.Println(message)
	
	v := <- ch

	fmt.Println(v)
}

// fatal error: all goroutines are asleep - deadlock!
//
// goroutine 1 [chan send]:
// main.main()
// 	/app/example.go:12 +0x4a

package main

import (
	"fmt"
)

func main() {
	ch := make(chan string)
	message := "hello"

	fmt.Println(message)
	
	ch <- message

	v := <- ch

	fmt.Println(v)
}

// hello
// fatal error: all goroutines are asleep - deadlock!
//
// goroutine 1 [chan send]:
// main.main()
// 	/app/example.go:12 +0x4a


从 code - 8 中我们可以确认,无缓存 channel 在发送消息后会堵塞当前线程直至该消息被接收后。

package main

import (
	"fmt"
)

func main() {
	ch := make(chan string)
	message := "hello"

	go func() {
		ch <- message
	}()

	v := <- ch

	fmt.Println(v)
}

// hello


从 code - 9 中我们可以确认,无缓存 channel 在接收消息前会堵塞当前线程直至收到该消息后。

package main

import (
	"fmt"
	"time"
)

func main() {
	ch := make(chan string)
	message := "World"

	go func() {
		fmt.Println("Hello")
		v := <- ch
		fmt.Println(v)
	}()

	time.Sleep(2 * time.Second)
	ch <- message
	fmt.Println("send")
	
	time.Sleep(2 * time.Second)
}

// Hello
// send
// World


那么我们通过刚刚的测试代码 code 6 ~ 9 中我们已经可以确定在 Unbuffered Channel 中接收者会阻塞直至接收到消息,发送者会阻塞直至接收者接收到消息。

从 code - 10 中我们可以确认在 buffered Channel 中,缓存没有被占满的情况下是不会对当前线程进行堵塞的。

package main

import (
	"fmt"
)

func main() {
	ch := make(chan string, 2)
	message := "Hello World"

	ch <- message

	v := <- ch

	fmt.Println(v)
}

// Hello World


从 code - 11 中我们可以确认在 buffered Channel 中,缓存被占满的情况下是会对当前线程进行堵塞的,那么接下来的结果可想而知了,在这里我们就不做过多描述了。

package main

import (
	"fmt"
)

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

	for i := 0; i <= cap(ch); i++ {
		ch <- i
	}

	v := <- ch

	fmt.Println(v)
}

// fatal error: all goroutines are asleep - deadlock!
//
// goroutine 1 [chan send]:
// main.main()
// 	/app/example.go:12 +0x4a

从 code - 12 中我们可以确认在 buffered Channel 中,发消息者与接收者的顺序为先发的先被接收。

package main

import (
	"fmt"
)

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

	for i := 1; i < cap(ch); i++ {
		ch <- i
	}

	for i := 1; i < cap(ch); i++ {
		v := <- ch
		fmt.Println(v)
	}
}

// 1
// 2
// 3


从 code -13 中我们可以确认,channel 在发送接收完消息后是可以继续工作的,并且对当前定义所传递数据类型中可以知道 channel 可以传递任意类型。

package main

import (
	"fmt"
	"time"
)

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

	go func() {
		message := "first"
		ch <- message
		v := <-ch
		fmt.Println(v)
	}()

	go func() {
		v := <-ch
		fmt.Println(v)
		message := "second"
		ch <- message
	}()

	time.Sleep(time.Second)
}


通过我们刚刚的测试代码相比我们已经对 channel 收发消息有了一定的了解,下面我们就来总结一下。


Channel Operate

在 Go 语言中 channel 默认是双向的,也就是既可以读也可以写,同时,我们还可以创建单向的 channel,单向 channel 也就是只能用于发送数据或者只能用于接收数据的 channel。

send-only

声明 send-only channel 如 code - 14 所示,只需要在 chan 关键字后面加上 <- 符号便可,如 code - 8 所示现在的 channel 只能用于发送数据,而不能用于接收数据。

var chanName chan<- chanType

package main

func main() {
	ch := make(chan<- string)
	<- ch 
}

// # command-line-arguments
// ./main.go:5:5: invalid operation: cannot receive from send-only channel ch (variable of type chan<- string)

receive-only

声明 receive-only channel 如 code - 16 所示,只需要在 chan 关键字前面加上 <- 符号便可,如 code - 10 所示现在的 channel 只能用于接收数据,而不能用于写数据。

var chanName <-chan chanType

package main

func main() {
	ch := make(<-chan string)
	ch <- "Hello"
}

// # command-line-arguments
// ./main.go:5:2: invalid operation: cannot send to receive-only channel ch (variable of type <-chan string)

both-way

声明 both-way channel 如 code - 18 所示,我们不需要在 chan 关键字前面或后面加符号便可,也就是我们最开始测试时使用的类型。

var chanName chan chanType

从 code - 11 中,我们可以确认 both-way channel 是可以任意转换成 receive-only channel 与 send-only channel 而反之则不可以。

package main

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

	var send chan<- string = ch
	send <- "Hello"

	var recv <-chan string = ch
	<- recv

	d1 := (chan int)(send)
	d2 := (chan int)(recv)
}

// # command-line-arguments
// ./main.go:12:19: cannot convert send (variable of type chan<- string) to type chan int
// ./main.go:13:19: cannot convert recv (variable of type <-chan string) to type chan int

所以只读或者只写 channel 的作用更像是规范双向 channel 在某个函数或进程中的使用,如 code - 20 所示使代码可读性更高,同时也提醒着使用者应该做什么。

package main

import (
	"fmt"
)

func receive(ch <-chan string) {
	v := <-ch
	fmt.Println(v)
}

func send(ch chan<- string) {
	message := "Hello"
	ch <- message
}

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

	go func() {
		send(ch)
	}()

	receive(ch)
}

// Hello

close

如 code - 21 所示 receive-only channe 是不可以被 close 的。

package main

func main() {
	both := make(chan string)
	send := make(chan<- string)
	receive := make(<-chan string)

	close(both)
	close(send)
	close(receive)
}

// # command-line-arguments
// ./main.go:10:8: invalid operation: cannot close receive-only channel receive (variable of type <-chan string)

如 code - 22 所示 channe 被 close 后是可以直接被读取的,读取是当前定义类型的初始值。

package main

import (
	"fmt"
)

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

	close(ch)

	fmt.Println(<- ch)
}

// 0

如 code - 23 所示 beffered channe 被 close 后也是可以读取值的,只不过 channel 中之前发送的数据会先被读取出来。

package main

import (
	"fmt"
)

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

	for i := 1; i < cap(ch); i++ {
		ch <- i
	}

	close(ch)

	for i := 1; i <= cap(ch); i++ {
		v := <- ch
		fmt.Println(v)
	}
}

// 1
// 2
// 3
// 0

如 code - 24 所示只读只写的 channe 被 close 后是不可以直接被读取的。

package main

import (
	"fmt"
)

func main() {
	send := make(chan<- int)
	receive := make(<-chan int)

	close(send)
	close(receive)

	fmt.Println(<- send)
	fmt.Println(<- receive)
}

// # command-line-arguments
// ./main.go:12:8: invalid operation: cannot close receive-only channel receive (variable of type <-chan int)
// ./main.go:14:17: invalid operation: cannot receive from send-only channel send (variable of type chan<- int)

Summarize

通过本周对 channel 的学习,相比我们已经对 channel 有了一定的了解,下面我们就来总结一下我们的测试结果。

  1. channel 类型不会对容量做出限制。
  2. channel 可以发送任意类型。
  3. 无缓存的 channel 在读取和写入时都会对我们当前的线程进行堵塞直到其他线程对其进行处理。
  4. channel 类型可以被定义为只读只写,多数情况下是规范双向 channel 在某个函数或进程中的使用。
  5. hannel 被 close 后依旧可以读值。
上次编辑于:
贡献者: ZEQUANR