在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
女主宣言 Go有两个重要的内置功能,同时也是它的特性。分别是channel、Goroutine。这两个特性使Go编写并发程序变的简单、有趣。本文将主要介绍channel。原文来自go101,本文是翻译后留存,方便自己学习。 PS:丰富的一线技术、多元化的表现形式,尽在“HULK一线技术杂谈”,点关注哦! Channel Introduction 不要通过共享内存来通信,应该通过通信来共享内存,出自Rob Pike。是在Go社区非常流行的一句话,而channel就是为此而生。 通过共享内存来通信与通过同通信来共享内存是两种模式的并发程序。当通过共享内存来通信,我们需要使用一些传统的并发技术,例如互斥锁,来保证共享内存可以被安全的访问,防止数据竞争。 channel是在多个Goroutine之间传递数据和同步的重要手段,而对通道的操作本身也是同步的。同一时刻,仅有一个Goroutine能向一个channel发送元素值,同时仅有一个Goroutine能从它那里接受元素值。在channel中,各个元素值都是严格按照发送到此的先后顺序排序,最早被发送至channel的元素值会最先被接收。它类似一个内部的FIFO(first in,first out先进先出)数据队列。 此外,channel中的元素值都具有原子性,是不可被分割的。channel中的每个元素都只能被某一个Goroutine接收,已被接收的元素值会立刻从channel中删除。 某些value的使用权会随着value在Goroutines间传递,当Goroutine发送数据到channel会释放value的所有权,而在接收数据时会同时获得value的所有权。 Go也支持一些传统的并发技术,但channel应该优先被考虑。 老实说,每个并发同步技术都有其最佳的使用场景。 但channel应用范围更广,使用场景更多。 而且在许多情况下,使用channel的并发代码通常比使用其他数据同步处理技术看起来更清晰和易于理解。 1 Channel 的分类 channel是复合类型,类似array、slice、map,每个channel都有一个元素类型。 所有要发送到通道的数据都必须是元素类型的值。channel可以为nil channel可分为双向、单向,假设以下的T是任意类型:
使用内置的make函数可以创建一个channel,下面这个例子会创建一个元素类型为int的channel,make函数的第二个参数为可选参数,可以设置channel的容量,默认为0。 2 Channel 的操作 这里有5个channel的操作,假设ch为channel类型
close是一个内置函数,参数必须是channel类型变量且不能是<-ch类型。 2. 发送值到channel
3. 从channel接收值 接收操作至少返回一个元素类型的值,它也可以用作赋值表达式 4. 查看channel容量 cap是一个内置函数,返回的值为int类型 5. 查看channel中当前值的数量 len是一个内置函数,返回的值为int类型。 如果channel元素类型为nil,cap、len将返回0。 3 Channel 操作规则总结 为了能将channel解释清楚,剩余的文章中会将channel分为三类:
下面这个表简单表述以上三类channel的操作场景 背景简介 表中5种未标记的场景,应用规则非常清晰:
未标记的4种场景在下面会详细解释 为了更好的理解channel,先了解下channel的内部结构。我们可以认为每个channel在内部维护3个队列
Channel 规则场景 A 当gorontine尝试从非nil未关闭channel接收元素值,该goroutine先尝试获得channel关联的锁,然后执行以下步骤,直到满足一个条件。
Channel 规则场景 B 当goroutine尝试发送元素值至非空未关闭的channel,该goroutine先尝试获取channel关联的锁,然后执行以下步骤,直到满足一个条件。
Channel 规则场景 C 当goroutine尝试关闭一个非空未关闭的channel,将按照以下顺序执行两个步骤
Channel 规则场景 D channel关闭后,channel的接收操作将不会再被阻塞。VBQ已有的元素值可以继续被接收。当VBQ中所有的元素值都被取出后,后续的接收操作都会收到元素值的零值。 通过上述的规则,我们可以得到一些事实
4 Channel 使用实例 现在来看些channel使用的例子 package main import "fmt" func main() { c := make(chan int) // 非缓冲通道 go func() { x := <- c // 这里会被阻塞,直到通道收到元素值 c <- x*x // 这里会被阻塞,直到通道中的值被接收 }() c <- 3 // 这里会被阻塞,直到通道中的值被接收 y := <-c // 这里会被阻塞,直到通道收到元素值 fmt.Println(y) // 9 } 下面这个例子使用了缓冲通道,但这个程序不是并发的 package main import "fmt" func main() { c := make(chan int, 2) // 缓冲通道 c <- 3 c <- 5 close(c) fmt.Println(len(c), cap(c)) // 2 2 x, ok := <-c fmt.Println(x, ok) // 3 true fmt.Println(len(c), cap(c)) // 1 2 x, ok = <-c fmt.Println(x, ok) // 5 true fmt.Println(len(c), cap(c)) // 0 2 x, ok = <-c fmt.Println(x, ok) // 0 false x, ok = <-c fmt.Println(x, ok) // 0 false fmt.Println(len(c), cap(c)) // 0 2 close(c) // panic! c <- 7 // also panic if the last line is removed. } 一场永不停止的足球赛 package main import ( "fmt" "time" ) func main() { var ball = make(chan string) kickBall := func(playerName string) { for { fmt.Println(<-ball, "kicked the ball.") time.Sleep(time.Second) ball <- playerName } } go kickBall("John") go kickBall("Alice") go kickBall("Bob") go kickBall("Emily") ball <- "referee" // kick off var c chan bool // nil <-c // blocking here for ever } 5 Channel 元素值通过拷贝传递 无论发送元素值至channel还是从channel接收元素值,都会对这个值进行拷贝。类似赋值、函数传参操作。 标准的go编译器,要求channel元素类型不能超过65535。通常,我们不会限制channel的大小,所有通过channel传送的元素值都会发生拷贝。当某个元素值从一个goroutine被传递到另外一个goroutine,会有2个元素值被拷贝。所以如果传送的值很大,最好还是用指针来代替。 6 Channel 中的 For-Range 循环 for-range代码结构也可以应用到channel。循环将尝试迭代的接收channel中的,直到channel被关闭并且VBQ为空。 for v = range aChannel { // use v } 等同于 for { v, ok = <-aChannel if !ok { break } // use v } 这里的aChannel不能是只允许发送的通道类型。如果它是一个nil channel, 循环将永久阻塞。 HULK一线技术杂谈 由360云平台团队打造的技术分享公众号,内容涉及云计算、数据库、大数据、监控、泛前端、自动化测试等众多技术领域,通过夯实的技术积累和丰富的一线实战经验,为你带来最有料的技术分享 |
请发表评论