值表示
通道类型是一个引用类型,所以初始化前,为此类型的零值:nil。
初始化通道
缓冲通道(10):
make(chan int,10)
make(chan int)
非缓冲通道:
make(chan int)
一个通道值的缓冲容量总是固定不变的。如果第二个参数值被省略了,就表示被初始化的这个通道永远无法缓冲任何元素值。发送给它的元素值应该被立刻取走,否则发送方的goroutine就会被暂停(或者说阻塞),直到有接收方接收这个元素值。
需要特别注意:
试图从一个未被初始化的通道值(即值为nil的通道)那里接收值,会造成当前goroutine的永久阻塞!
同样,向nil 通道 发送值,goroutine也会被永久阻塞!
操作特性
通道是在多个goroutine之间传递数据和同步数据的重要手段,而对通道的操作本身也是同步的。
在同一时刻,仅有一个goroutine能向一个通道发送值,
同时也仅有一个goroutine能接收值。
在通道中,先进先出,相当于FIFO的消息队列。
通道中的每个值都只可能被某一个goroutine接收,已接受的值会立刻从通道中删除。
Go语言中 空结构类型的变量是不占用任何内存空间的,
并且所有该类型的变量都拥有相同的内存地址。
建议用于传递"信号"的通道,都以struct{}作为元素类型,除非需要传递更多信息。
package main
import (
"fmt"
"time"
)
var strChan = make(chan string, 3)
func main() {
// 通知接受操作chan
syncChan1 := make(chan struct{}, 1)
// 避免主goroutine过早结束
syncChan2 := make(chan struct{}, 2)
go func() { // 用于接收操作。
<-syncChan1
fmt.Println("Received a sync signal and wait a second... [receiver]")
time.Sleep(time.Second)
for {
if elem, ok := <-strChan; ok {
fmt.Println("Received:", elem, "[receiver]")
} else {
break
}
}
fmt.Println("Stopped. [receiver]")
syncChan2 <- struct{}{}
}()
go func() { // 用于发送操作。
for _, elem := range []string{"a", "b", "c", "d"} {
strChan <- elem
fmt.Println("Sent:", elem, "[sender]")
if elem == "c" {
syncChan1 <- struct{}{}
fmt.Println("Sent a sync signal. [sender]")
}
}
fmt.Println("Wait 2 seconds... [sender]")
time.Sleep(time.Second * 2)
close(strChan)
syncChan2 <- struct{}{}
}()
<-syncChan2
<-syncChan2
}
//输出
Sent: a [sender]
Sent: b [sender]
Sent: c [sender]
Sent a sync signal. [sender]
Received a sync signal and wait a second... [receiver]
Received: a [receiver]
Received: b [receiver]
Received: c [receiver]
Received: d [receiver]
Sent: d [sender]
Wait 2 seconds... [sender]
Stopped. [receiver]
除此之外,如果有多个goroutine因向同一个已满的通道发送元素值而被阻塞,那么当该通道中有多余空间的时候,最早被阻塞的那个goroutine会最先被唤醒。对于接收操作来说,也是如此,一旦已空的通道中有了新的元素值,那么最早因从该通道接收元素值而被阻塞的那个goroutine会最先被唤醒。
并且, Go运行时系统每次只会唤醒一个goroutine。
还有一点需要注意:发送方向通道发送的值会被复制,接收方接收的总是该值的副本,而不是该值本身。经由通道传递的值至少会被复制一次,至多会被复制两次。
例如,当向一个已空的通道发送值,且已有至少一个接收方因此等待时,该通道会绕过本身的缓冲队列,直接把这个值复制给最早等待的那个接收方。
又例如,当从一个已满的通道接收值,且已有至少一个发送方因此等待时,该通道会把缓冲队列中最早进入的那个值复制给接收方,再把最早等待的发送方要发送的数据复制到那个值的原先位置上。通道的缓冲队列属于环形队列,所以这样做是没问题的。
这种情况下,涉及的那两个值在传递到接收方之前都会被复制两次。
因此,当接收方从通道接收到一个值类型的值时,对该值的修改就不会影响到发送方持有的那个源值。但对于引用类型的值来说,这种修改会同时影响收发双方持有的值。
// chanval1.go
package main
import (
"fmt"
"time"
)
var mapChan = make(chan map[string]int, 1)
func main() {
syncChan := make(chan struct{}, 2)
go func() { // 用于演示接收操作。
for {
if elem, ok := <-mapChan; ok {
elem["count"]++
} else {
break
}
}
fmt.Println("Stopped. [receiver]")
syncChan <- struct{}{}
}()
go func() { // 用于演示发送操作。
countMap := make(map[string]int)
for i := 0; i < 5; i++ {
mapChan <- countMap
time.Sleep(time.Millisecond)
fmt.Printf("The count map: %v. [sender]\n", countMap)
}
close(mapChan)
syncChan <- struct{}{}
}()
<-syncChan
<-syncChan
}
//输出
The count map: map[count:1]. [sender]
The count map: map[count:2]. [sender]
The count map: map[count:3]. [sender]
The count map: map[count:4]. [sender]
The count map: map[count:5]. [sender]
Stopped. [receiver]
chan val 值类型
// chanval2.go
package main
import (
"fmt"
"time"
)
// Counter 代表计数器的类型。
type Counter struct {
count int
}
// 引用类型,其值类型属性不会被修改
var mapChan = make(chan map[string]Counter, 1)
//var mapChan = make(chan map[string]*Counter, 1)
func main() {
syncChan := make(chan struct{}, 2)
go func() { // 用于演示接收操作。
for {
if elem, ok := <-mapChan; ok {
counter := elem["count"]
counter.count++
} else {
break
}
}
fmt.Println("Stopped. [receiver]")
syncChan <- struct{}{}
}()
go func() { // 用于演示发送操作。
countMap := map[string]Counter{
"count": Counter{},
}
//countMap := map[string]*Counter{
// "count": &Counter{},
//}
for i := 0; i < 5; i++ {
mapChan <- countMap
time.Sleep(time.Millisecond)
fmt.Printf("The count map: %v. [sender]\n", countMap)
}
close(mapChan)
syncChan <- struct{}{}
}()
<-syncChan
<-syncChan
}
//输出
The count map: map[count:{0}]. [sender]
The count map: map[count:{0}]. [sender]
The count map: map[count:{0}]. [sender]
The count map: map[count:{0}]. [sender]
The count map: map[count:{0}]. [sender]
Stopped. [receiver]
chan val 引用类型
package main
import (
"fmt"
"time"
)
// Counter 代表计数器的类型。
type Counter struct {
count int
}
//var mapChan = make(chan map[string]Counter, 1)
// 指针引用类型的指针类型,其值类型属性 会被修改
var mapChan = make(chan map[string]*Counter, 1)
func main() {
syncChan := make(chan struct{}, 2)
go func() { // 用于演示接收操作。
for {
if elem, ok := <-mapChan; ok {
counter := elem["count"]
counter.count++
} else {
break
}
}
fmt.Println("Stopped. [receiver]")
syncChan <- struct{}{}
}()
go func() { // 用于演示发送操作。
//countMap := map[string]Counter{
// "count": Counter{},
//}
countMap := map[string]*Counter{
"count": &Counter{},
}
for i := 0; i < 5; i++ {
mapChan <- countMap
time.Sleep(time.Millisecond)
fmt.Printf("The count map: %v. [sender]\n", countMap)
}
close(mapChan)
syncChan <- struct{}{}
}()
<-syncChan
<-syncChan
}
The count map: map[count:0xc00001a078]. [sender]
The count map: map[count:0xc00001a078]. [sender]
The count map: map[count:0xc00001a078]. [sender]
The count map: map[count:0xc00001a078]. [sender]
The count map: map[count:0xc00001a078]. [sender]
Stopped. [receiver]
关闭通道
package main
import "fmt"
func main() {
dataChan := make(chan int, 5)
syncChan1 := make(chan struct{}, 1)
syncChan2 := make(chan struct{}, 2)
go func() { // 用于演示接收操作。
<-syncChan1
for {
if elem, ok := <-dataChan; ok {
fmt.Printf("Received: %d [receiver]\n", elem)
} else {
break
}
}
fmt.Println("Done. [receiver]")
syncChan2 <- struct{}{}
}()
go func() { // 用于演示发送操作。
for i := 0; i < 5; i++ {
dataChan <- i
fmt.Printf("Sent: %d [sender]\n", i)
}
close(dataChan)
syncChan1 <- struct{}{}
fmt.Println("Done. [sender]")
syncChan2 <- struct{}{}
}()
<-syncChan2
<-syncChan2
}