channel是Go语言中的一个核心数据类型,channel是一个数据类型,主要用来解决协程的同步问题以及协程之间数据共享(数据传递)的问题。在并发核心单元通过它就可以发送或者接收数据进行通讯,这在一定程度上又进一步降低了编程的难度。

      goroutine运行在相同的内存地址空间,channel可以避开所有内存共享导致的坑;通道的通信方式保证了同步性。数据通过channel:同一时间只有一个协程可以访问数据:所以不会出现数据竞争,确保并发安全。

channel的定义

make(chan Type, capacity)
channel := make(chan bool) //创建一个无缓冲的bool型Channel
,等价于make(chan Type, 0)
channel := make(chan bool, 1024) //创建一个有缓冲,切缓冲区为1024的bool型Channel


channel <- x           //向一个Channel发送一个值
<- channel             //从一个Channel中接收一个值
x = <- channel         //从Channel c接收一个值并将其存储到x中
x, ok = <- channel     //从Channel接收一个值,如果channel关闭了或没有数据,那么ok将被置为false

      channel是一个引用类型,当复制一个channel或用于函数参数传递时,我们只是拷贝了一个channel引用,因此调用者和被调用者将引用同一个channel对象。和其它的引用类型一样,channel的零值(定义未初始化)也是nil。

channel <- 1str := <-channel

示例

package main

import (
	"fmt"
	"runtime"
	"time"
)

var c = make(chan int32)

func printstr(s string) {
	for _, value := range s {
		fmt.Printf("写入%+q\r\n", value)
		time.Sleep(time.Second)
		c <- value
	}
}

func main() {
	runtime.GOMAXPROCS(1)
	go func() {
		time.Sleep(time.Second)
		printstr("hello")
	}()

	go func() {
		for v := range c {
			fmt.Printf("读取%+q\r\n", v)
		}
	}()
	for {
              ;
	}
}

channel的缓冲

无缓冲的channel

unbuffered channel
  • 阻塞:由于某种原因数据没有到达,当前协程(线程)持续处于等待状态,直到条件满足,才接触阻塞。
  • 同步:在两个或多个协程(线程)间,保持数据内容一致性的机制。

示例如上,写了没有读会导致阻塞,读了没有写会导致堵塞

有缓冲的channel

buffered channelgoroutine
  • 只有channel通道中没有要接收的值时,接收动作才会阻塞。
  • 只有通道没有可用缓冲区容纳被写入(发送)的值时,发送动作才会阻塞。
goroutine

示例

package main

import (
	"fmt"
	"runtime"
	"time"
)

var c = make(chan int32, 10)

func printstr(s string) {
	for _, value := range s {
		fmt.Printf("写入%+q\r\n", value)
		c <- value
	}
}

func main() {
	runtime.GOMAXPROCS(1)
	go func() {

		printstr("hello")
	}()

	go func() {

		time.Sleep(time.Second * 2)
		fmt.Println("读通道开始读取数据")
		for v := range c {
			fmt.Printf("读取%+q\r\n", v)
		}
	}()
	for {

	}
}

      结果可以看出,如果给定了一个缓冲区容量,channel就是异步的。只要缓冲区有未使用空间用于发送数据,或还包含可以接收的数据,那么其通信就会无阻塞地进行。

channel的关闭

close()

示例

package main

import (
	"fmt"
	"runtime"
	"time"
)

var c = make(chan int32, 10)

func printstr(s string) {
	for _, value := range s {
		fmt.Printf("写入%+q\r\n", value)
		c <- value
	}
	close(c)
}

func main() {
	runtime.GOMAXPROCS(1)
	go func() {
		printstr("hello")
	}()

	time.Sleep(time.Second * 2)
	fmt.Println("读通道开始读取数据")
	for {
		if char, ok := <-c; ok {
			fmt.Printf("读取%+q\r\n", char)
		} else {
			break
		}
	}
}

提示

  • channel不像文件一样需要经常去关闭,只有当你确实没有任何发送数据了,或者你想显式的结束range循环之类的,才去关闭channel;
  • 关闭channel后,无法向channel 再发送数据(引发 panic 错误后导致接收立即返回零值);
  • 关闭channel后,可以继续从channel接收数据(读取到的数据为channel类型的默认值,如int默认值0 string默认值"");
  • 对于nil channel,无论收发都会被阻塞。

缓冲channel 和 非缓冲channel的区别

make(chan TYPE,CAPCTIY)make(chan TYPE)

单项channel及应用

默认情况下,channel是双向的,既可以往里面发送数据也可以接收数据。但是,常将channel作为参数进行传递而只希望对方是单向使用的,要么只让它发送数据,要么只让它接收数据,这时候可以指定通道的方向。

单项channel的声明

ch = make(chan int)var ch chan <- intch = make(chan <- int)var ch <- chan intch = make(<-chan int)

可以将 channel 隐式转换为单向队列,只收或只发,不能将单向 channel 转换为普通 channel,示例:

package main

import (
	"fmt"
	"runtime"
)

var c = make(chan string, 10)

func read(c <-chan string) {
	fmt.Println("读通道开始读取数据")
	for {
		if char, ok := <-c; ok {
			fmt.Printf("读取%s\r\n", char)
		} else {
			break
		}
	}
}

func write(ch chan<- string, str []string) {
	defer close(ch)
	for _, value := range str {
		fmt.Printf("写入%+q\r\n", value)
		ch <- value
	}
}

func main() {
	runtime.GOMAXPROCS(3)

	go write(c, []string{"h", "e", "l", "l", "o"})
	read(c)
}