Golang

关注公众号 jb51net

关闭
首页 > 脚本专栏 > Golang > Golang Channel

详解Golang中Channel的原理和使用技巧

作者:AllenWu

Channel管道提供了一种机制,它在两个并发执行的协程之间进行同步,并通过传递与该管道元素类型相符的值来进行通信。本文主要介绍了Channel的原理和使用技巧,需要的可以参考一下

Channel 详解

Channel 简要说明

Channel(一般简写为 chan) 管道提供了一种机制,它在两个并发执行的协程之间进行同步,并通过传递与该管道元素类型相符的值来进行通信。Channel 是用来在不同的 goroutine 中交换数据的,千万不要把 Channel 拿来在同一个 goroutine 中的不同函数之间间交换数据,chan 可以理解为一个管道或者先进先出的队列。

Channel 类型定义

最简单形式: chan elementType,通过这个类型的值,你可以发送和接收elementType 类型的元素。Channel 是引用类型,如果将一个 chan 变量赋值给另外一个,则这两个变量访问的是相同的 chann。

当然,我们可以用 make 分配一个channel:var c = make(chan int)

Channel 操作符<- 和操作方式

通信操作符 <- 的箭头指示数据流向,箭头指向哪里,数据就流向哪里,它是一个二元操作符,可以支持任意类型,对于 channel 的操作只有4种方式:

1.创建 channel (通过make()函数实现,包括无缓存 channel 和有缓存 channel);

2.向 channel 中添加数据(channel<-data);

3.从 channel 中读取数据(data<-channel);

4.关闭 channel(通过 close()函数实现)

Channel 有无缓冲 & 同步、异步

channel 分为有缓冲 channel 和无缓冲 channel,两种 channel 的创建方法如下:

var ch = make(chan int) //无缓冲 channel,等同于make(chan int ,0),是一个同步的 Channel

var ch = make(chan int,10) //有缓冲channel,缓冲大小是10,是一个异步的Channel

Channel 各种操作导致阻塞和协程泄漏的场景

写操作,什么时候会被阻塞?

1.向 nil 通道发送数据会被阻塞

2.向无缓冲 channel 写数据,如果读协程没有准备好,会阻塞

3.向有缓冲 channel 写数据,如果缓冲已满,会阻塞

有缓冲的 channel,在缓冲 buffer 之内,不读取也不会导致阻塞,当然也就不会使得协程泄漏,但是如果写数据超过了 buffer 还没有读取,那么继续写的时候就会阻塞了。如果往有缓冲的 channel 写了数据但是一直没有读取就直接退出协程的话,一样会导致 channel 阻塞,从而使得协程阻塞并泄漏。

读操作,什么时候会被阻塞?

close 操作,什么时候会被阻塞?

close channel 对 channel 阻塞是没有任何效果的,写了数据但是不读,直接 close,还是会阻塞的。

Channel 各种操作对应的状态

Channel 长度和容量

容量(capacity)代表 Channel 容纳的最多的元素的数量,代表Channel的缓存的大小。如果没有设置容量,或者容量设置为0, 说明 Channel 没有缓存,长度和容量的两个函数是 cap 和 len 。

示例如下:

c := make(chan int, 100) // cap 就是 100,但是此时 len 为 0
c <- 0  // len = 1, cap = 100
c <- 0  // len = 2, cap = 100
<- c    // len = 1, cap = 100

Channel 的缺点

Channel 的缺点:

Channel 可能会导致循环阻塞或者协程泄漏,这个是最最最要重点关注的。

Channel 中传递指针会导致数据竞态问题(data race/ race conditions)

Channel 中传递的都是数据的拷贝,可能会影响性能,但是就目前我们的机器性能来看,这点数据拷贝所带来的 CPU 消耗,大多数的情况下可以忽略。

Go Channel 实现协程同步

channel 实现并发同步的说明

channel 作为 Go 并发模型的核心思想:不要通过共享内存来通信,而应该通过通信来共享内存,那么在 Go 里面,当然也可以很方便通过 channel 来实现协程的并发和同步了,并且 channel 本身还可以支持有缓冲和无缓冲的,通过 channel + timeout 实现并发协程之间的同步也是常见的一种使用姿势。

无缓冲 chan 示例

示例如下:

package main
import "fmt"
func main() {
     var ch = make(chan string)
     for i := 0; i < 10; i++ {
             go sum(i, i+10, ch)
     }
     for i := 0; i < 10; i++ {
             fmt.Print(<-ch)
     }
}
func sum(start, end int, ch chan string) {
     var sum int = 0
     for i := start; i < end; i++ {
             sum += i
     }
     ch <- fmt.Sprintf("Sum from %d to %d is %d\n", start, end, sum)
}

有缓冲 chan 示例

	message_chan := make(chan int, 2)

	go func() {
		time.Sleep(time.Second * 3)
		println("start recv...")
		println(<-message_chan)
		println(<-message_chan)
		println(<-message_chan)
		println("finish recv...")
	}()

	println("start send 10...")
	message_chan <- 10

	println("start send 20...")
	message_chan <- 20

	println("start send 30...")
	message_chan <- 30

	println("finish send...")

	time.Sleep(time.Second * 3)
	close(message_chan)

到此这篇关于详解Golang中Channel的原理和使用技巧的文章就介绍到这了,更多相关Golang Channel内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文