likes
comments
collection
share

还记得 Go 并发的风采吗?一起来回顾一下 go 并发的基础语法

作者站长头像
站长
· 阅读数 26

并发编程 程序有若干个自主的活动单元组成 从分利用现代多喝计算机的性能

Go 并发风格

支持_通信顺序进程_(Communicating Sequential Process, CSP),CSP 是一个并发的模式,在不同的执行体(e.g. goroutine)之间传递值,但是变量本身局限于单一的执行体

goroutine 协程

  • Go 程序并发的执行体
  • goroutine: Go 程序中的每一个并发活动
  • 当程序启动时,只有一个 goroutine 来调用main函数,成为主协程
  • 通过 go 关键字创建 goroutine
func main() {
  go spinner(100 * time.Millisecond)	// 开启一个新的 goroutine
  const n = 45
  fibN := fib(n)
  fmt.Printf("\rFibonacci(%d) = %d\n", n, fibN)
}

func spinner(delay time.Duration) {
  for {	// 死循环
    // 循环输出 -\|/
    for _, r := range `-\|/` {
      fmt.Printf("\r%c", r)
      time.Sleep(delay)
    }
  }
}

// 计算斐波那契数列
func fib(x int) int {
  if x < 2 {
    return
  }
  return fib(x - 1) + fib(x - 2)
}

channel 通道/管道

  • goroutine 之间的连接
  • channel 让一个 goroutine 发送特定值道另一个 goroutine通信机制
  • channel 是一个具有具体类型的通道,成为通道的元素类型
  • **channel**** 支持的操作**
    • make 创建
// 无缓冲通道
ch := make(chan int)
ch := make(chan int, 0)

// 容量为 3 的缓冲管道
ch := make(chan int, 3)
  • send 发送
    • channel 关闭后,任何后续的发送操作都会导致崩溃在 channel 关闭后,任何后续的发送操作都会导致崩溃
ch <- x
  • receive 接收
    • channel 关闭后,任何接收操作都可以正常执行,接收到的值都是零值
// 赋值语句中的接收表达式
x = <- ch

// 丢弃结果
<- ch
  • close 关闭
    • 通道的关闭并不是必须的,通道可以通过垃圾回收机制根据是否可以访问来决定是否会回收,而不是是否被关闭
    • 关闭一个已经关闭的通道会导致宕机
close(ch)

如何判断一个通道是否关闭 接收操作返回两个结果:

  • 接收到的通道元素
  • 一个布尔值
    • true - 接收成功
    • false 接受操作在一个关闭并且读取完的通道上
go func() {
    for {
        x, ok := <- naturals
        if !ok {
            break
        }
        squares <- x * x
    }
    close(squares)
}()

无缓冲通道

无缓冲通道上的发送操作将会阻塞,直到另一个 goroutine 在对应的通道上执行接收操作 如果接受操作先执行,接收方的 goroutine 也会阻塞,直到另一个 goroutine 在同一个通道发送值

意义 使发送和接收的 **goroutine** 同步化 因此无缓冲通道也被称为同步通道

func main() {
    // 建立网络连接
    conn, err := net.Dial("tcp", "localhost:8888")
    if err != nil {
        log.Fatal(err)
    }

    // 构建一个无缓冲通道
    done := make(chan struct{})
    // 新建一个 goroutine
    go func() {
        // 输出拷贝到请求
        io.Copy(os.Stdout, conn)
        // 执行结束
        log.Println("done")
        done <- struct{}{}
    }()
    // 拷贝请求到输入
    mustCopy(conn, os,Stdin)
    // 关闭请求
    conn.Close()
    // 等待 goroutine 完成
    <- done
}

func mustCopy(dst io.Wtirer, src io.Reader) {
    if _, err := io.Copy(dst, src); err != nil {
        log.Fatal(err)
    }
}

单向通道

当一个通道用作函数形参是,通常会被限制不能发送或者接收

  • 双向通道可以转换为单向通道,但单向通道不能转换为双向通道
  • 关闭一个仅能接受的通道在编译时会报错
func main() {
    naturals := make(chan int)
    squares := make(chan int)

    go counter(naturals)
    go squareer(squares, naturals)
    printer(squares)
}

func counter(out chan<- int) {
    for x := 0; x < 100; x++ {
        out <- x
    }
    close(out)
}

func squarer(out chan<- int, in <-chan int) {
    for v := range in {
        out <- v * v
    }
}

func printer(in <- chan int) {
    for  := range in {
        fmt.Println(v)
    }
}

缓冲通道

还记得 Go 并发的风采吗?一起来回顾一下 go 并发的基础语法

ch = make(chan string, 3)	// 创建一个缓冲区为 3 的缓冲通道
  • 缓冲通道上的发送操作会在队列尾部插入一个元素,接受操作从队列的同步移除一个元素
  • 如果通道缓冲区为0(通道已满),发送操作会阻塞,直至接受操作执行
  • 如果通道为0(通道为空),则接收操作会阻塞,直至发送操作执行
  • 获取缓冲区容量 cap()
  • 获取通道内元素个数 len()

goroutine 泄漏

func mirroredQuery() string {
    responses := make(chan string, 3)
    go func() { responses <- request("asia.gopl.io") }()
    go func() { responses <- request("asia.gopl.io") }()
    go func() { responses <- request("asia.gopl.io") }()
    return <- responses	// 返回相应最快的结果
}

func request (hostname string) (response string) { /* ... */ }

在上述事例中,如果使用一个无缓冲通道,由于发送相应结果到通道时,没有goroutine 来接收,因此比较慢的两个goroutine 将被卡住,这种情况称为goroutine泄漏

缓冲通道与无缓冲通道的选择

  • 无缓冲通道提供强同步保证
  • 缓冲通道可以解藕操作,但是在内存无法提供缓冲容量的情况下,可能导致程序死锁
  • 缓冲可能会影响程序性能
转载自:https://juejin.cn/post/7377683138238038079
评论
请登录