Golang中channel总结


golang里面倡导的是“通过通信来共享内存”,简单来说channel是groutine之间通信的桥梁。

1. 基本操作

  • 声明
1
2
3
4
5
6
7
8
9
10
11
12
// 无缓冲channel
// 方式一
ch := make(chan DataType)
// 方式二
var ch chan DataType

// 有缓冲channel
// 方式一
ch := make(chan DataType, n)
// 方式二
var ch chan DataType
ch = make(chan DataType, n)
  • 发送与接收
1
2
3
4
5
6
ch := make(chan int)
// 发送
ch <- 1

// 接收
n := <- ch
  • 注意:channel是goroutine之间通信的机制

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main

    import "fmt"

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

    ch <- 1

    fmt.Println(<-ch)
    }

    报错:fatal error: all goroutines are asleep - deadlock!

    channel是不同goroutine之间通信的机制,同一个goroutine里面发送和接收是会造成阻塞的。修改如下就没问题了:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    package main

    import "fmt"

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

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

    fmt.Println(<-ch)
    }

2. 无缓冲channel与有缓冲channel

2.1 关于阻塞
  • 无缓冲channel的发送和接收是同步的
  • 无缓冲channel的接收在发送之前阻塞
  • 在已经包含数据的无缓冲channel的发送在接收之前阻塞
  • 有缓冲channel的发送和接收可以是异步的
  • 当有缓冲channel队列满了时,发送阻塞
  • 当有缓冲channel为空时,接收阻塞
2.2 关闭之后操作
  • 重复关闭 channel 会导致 panic。

    1
    panic: close of closed channel
  • 向关闭的 channel 发送数据会 panic。

    1
    panic: send on closed channel
  • 从关闭的 channel 读数据不会 panic,读出 channel 中已有的数据之后再读就是 channel 类似的默认值,比如 chan int 类型的 channel 关闭之后读取到的值为 0。

3. channel死锁的几种常见情况

  • 同一个goroutine中对同一个channel进行读写操作(上面已经提到)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    package main

    import "fmt"

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

    ch <- 1

    fmt.Println(<-ch)
    }
  • 在两个以上的goroutine中,channel的读写早于goroutine的创建。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    package main

    import (
    "fmt"
    )

    func main() {
    ch := make(chan int)
    ch <- 12
    go func() {
    fmt.Println(<-ch)
    }()

    }
  • 多个goroutine使用多个channel进行通信,channel之前互相等待对方状态造成死锁。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    package main

    func main() {
    ch1 := make(chan int)
    ch2 := make(chan int)

    go func() {
    for {
    select {
    case <-ch1:
    ch2 <- 2
    }
    }
    }()

    for {
    select {
    case <-ch2:
    ch1 <- 1
    }
    }
    }

3. channel常见用法

3.1 通信
1
2
3
4
5
6
7
8
9
10
11
12
13
package main

import "fmt"

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

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

fmt.Println(<-ch)
}
3.2 range遍历
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package main

import (
"fmt"
)

func main() {
ch := make(chan int, 3)
go func() {
for i := 1; i < 4; i++ {
ch <- i
}

close(ch)
}()

for c := range ch {
fmt.Println(c)
}
}

range c产生的迭代值为Channel中发送的值,它会一直迭代直到channel被关闭。上面的例子中如果把close(c)注释掉,程序会一直阻塞在for …… range那一行。

3.3 超时处理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package main

import (
"fmt"
"time"
)

func main() {
ch := make(chan int)
go func() {
time.Sleep(time.Second * 6)
ch <- 99
}()

select {
case re := <-ch:
fmt.Println("ch: ", re)
case <-time.After(time.Second * 5):
fmt.Println("timeout")
}

}

使用time.After()返回一个类型为<-chan Time的单向的channel。