sunwenfei
  • 关于我
  • Golang
    • Goglang基础教程【译】
      • 介绍
        • 安装
        • Hello World
      • 变量、基本类型以及常量
        • 变量
        • 基本类型
        • 常量
      • 函数和包
        • 函数
        • 包
      • 条件、循环流程控制语句
        • if else条件语句
        • switch语句
        • 循环语句
      • 数组、切片、变参函数
        • 数组(Array)
        • 切片(Slice)
        • 变参函数
      • 其他数据类型
        • 映射(Map)
        • 字符串
      • 指针、结构体和方法
        • 指针
        • 结构体
        • 方法
      • 面向对象编程
        • 结构体 vs 类
        • 组合 vs 继承
        • 接口
        • 多态
      • 并发
        • 并发介绍
        • 协程(goroutine)
        • 管道(channel)
        • 带缓存的管道(buffered channel)
        • 协程池
        • 管道选择器(select)
        • 互斥锁(Mutex)
      • Defer
      • 一等公民函数
      • 反射
      • 错误
        • 错误处理
        • 自定义错误类型
        • panic和recover
      • 文件读写
        • 读文件
        • 写文件
    • Golang面向对象编程
    • Golang函数式编程
    • Golang并发编程
    • Golang web服务编程
    • Golang数据结构与算法
  • Shell编程
    • Find命令
  • JavaScript
    • browser
    • Node.JS
    • Deno
  • TypeScript
  • HTTP
    • 【译】通过信鸽理解HTTPS交互原理
  • React
    • React16
      • Hooks
        • 使用React Hooks拉取数据
  • 移动端开发
    • 原生
    • Flutter
    • ReactNative
    • 小程序
  • 前端测试
Powered by GitBook
On this page

Was this helpful?

  1. Golang
  2. Goglang基础教程【译】
  3. 并发

带缓存的管道(buffered channel)

上一节我们讨论的channel都是不带缓存的,这里所谓的不带缓存的意思是写进去一个数据,就必须先读取走才能接着写下一个数据进去,因此也是阻塞的。

Golang中我们还可以创建带缓存的channel,向带缓存的channel中写数据时,只有写满缓存才会阻塞协程的执行;同样的,从带缓存的channel中读数据时,只有缓存为空时,即已经没数据可读时才会阻塞协程的执行。

可以通过如下语法来创建带缓存的channel:

ch := make(chan type, capacity)

这里的capacity表示缓存容量,必须大于或等于1才有缓存,默认的不带缓存的channel缓存容量为0。

带缓存channel举例

我们举个例子来体会下带缓存channel的用法。

package main

import "fmt"

func main() {
    ch := make(chan string, 2)
    ch <- "naveen"
    ch <- "paul"
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

这里我们通过语句ch := make(chan string, 2)创建了缓存容量为2的channel。由于容量为2,我们可以向该channel中写入2个数据而不阻塞协程的执行。我们写入了两个字符串,由于没有阻塞,因此后面的打印语句得以执行。该程序输出结果如下:

naveen  
paul

我们再看一个例子来加深下对带缓存channel的理解。

package main

import (
    "fmt"
    "time"
)

func write(ch chan int) {
    for i := 0; i < 5; i++ {
        ch <- i
        fmt.Println("successfully wrote", i, "to ch")
    }
    close(ch)
}
func main() {
    ch := make(chan int, 2)
    go write(ch)
    time.Sleep(2 * time.Second)
    for v := range ch {
        fmt.Println("read value", v, "from ch")
        time.Sleep(2 * time.Second)
    }
}

这里我们创建一个缓存容量为2的channel。开启了write协程后,主协程暂停了2秒。这期间write协程在并发执行,在write协程内我们不断的向channel内写入数据,但是由于channel的容量为2,我们仅能先写进去2个数据,即0和1,然后该协程进入阻塞状态,直到channel内的数据被读走。因此该程序先输出如下:

successfully wrote 0 to ch  
successfully wrote 1 to ch

然后主协程暂停2秒后开始读取channel内的数据,每读出一个数据主协程又要暂停2秒,这个暂停期间write协程中发现channel内多了一个缓存位置,便继续写入一个数据,然后缓存又变满,再次进入阻塞状态。该程序输出如下:

successfully wrote 0 to ch
successfully wrote 1 to ch
read value 0 from ch
successfully wrote 2 to ch
read value 1 from ch
successfully wrote 3 to ch
read value 2 from ch
successfully wrote 4 to ch
read value 3 from ch
read value 4 from ch

死锁

上一节我们已经介绍过死锁了:

如果某个goroutine向某个channel发送数据,那么需要某个其他goroutine接收该channel的数据,否则会发送运行时错误:deadlock。类似的,如果某个goroutine正在等待从某个channel中接收数据,那么需要某个其他goroutine向该channel中写入数据,否则也会发送运行时错误:deadlock。

上一节我们讨论的channel都是不带缓存的,因此前面的说法是成立的。对于带缓存的channel,死锁条件则是缓存满了,或者缓存空了。就是说,如果channel缓存已经满了,再继续向channel写入数据时,那么就需要存在某个其他goroutine接收该channel的数据,否则陷入死锁状态;如果channel缓存已经空了,再继续从该channel中读取数据时,需要存在某个其他goroutine向该channel中写入数据,否则陷入死锁状态。

package main

import "fmt"

func main() {
    ch := make(chan string, 2)
    ch <- "naveen"
    ch <- "paul"
    ch <- "steve"
    fmt.Println(<-ch)
    fmt.Println(<-ch)
}

这里我们向一个缓存容量为2的channel中写入了3个数据。这时必须有某个goroutine从该channel中读取数据才行,但是并没有,因此陷入死锁状态。该程序执行报如下错误:

fatal error: all goroutines are asleep - deadlock!

goroutine 1 [chan send]:
main.main()
        /Users/goWorkspace/src/test/main.go:9 +0x90
exit status 2

channel长度 vs channel容量

channel容量指的是channel最多能缓存的数据个数,是我们用make初始化channel时的第二个参数。channel长度指的是channel当前时刻缓存的数据个数。

package main

import "fmt"

func main() {
    ch := make(chan string, 3)
    ch <- "naveen"
    ch <- "paul"
    fmt.Println("capacity is", cap(ch))
    fmt.Println("length is", len(ch))
    fmt.Println("read value", <-ch)
    fmt.Println("new length is", len(ch))
}

这里我们创建了一个容量为3的channel,因此最多能缓存3个数据。接着我们写进去2个数据,这样缓存内就有2个数据,因此长度为2。然后又读取了1个数据,这时还剩下1个数据,因此长度为1。该程序执行结果如下:

capacity is 3
length is 2
read value naveen
new length is 1
Previous管道(channel)Next协程池

Last updated 5 years ago

Was this helpful?