0%

gopl-ch8

Chapter

Goroutines and Channels

Goroutines

  1. 第一个gorountinemain

  2. 使用go关键字,例子如下

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

    import (
    "fmt"
    "time"
    )

    func main() {
    go spinner(100 * time.Millisecond)
    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 x
    }
    return fib(x-1) + fib(x-2)
    }
  3. 除了退出之外没有其他办法使得一个gorountine停止另外一个,当然,依然可以通过gorountine之间的交流来请求对方停止

  1. 我们常常使用函数字面量的方式,来启动一个gorountine,即go 函数定义(传入参数),而这个过程又常常涉及到闭包,由于函数字面量可能在函数体中引用外部的变量,如channel

    Example: Concurrent Clock Server

    Example: Concurrent Echo Server

Channels

  1. 创建一个channel

    1
    ch := make(chan int) // 创建了一个名为ch的channel,其中传递的数据类型为int
  2. channel的基本操作

    • Send:
      ch <- x ,将x的值传入ch
    • Receive
      x = <-ch, 从ch接收值并赋给变量x,也可以不设置接收对象,应该等同于_ = <-ch,即抛弃接收的值

      作为函数返回值的时候不需要声明临时变量,return <- ch即可

    • Close
      close(ch)将会关闭一个channel,对一个已经关闭的channel进行
      • Send操作将会产生panic
      • Receive操作将会获取可能已经传入的值,如果没有则获得的为对应类型的零值,(比如发送方在send之后立即关闭,此时可能接收方还没有收到,但channel已经被关闭,那么接收方依然可以对这个channel执行receive操作,直到没有值可以取出,可以理解为邮箱?)。在Receive操作中可以选择接收两个值,x, ok = <-ch其中第二个值 ok就可以用来表征是否已经取出了所有的值。当然由于这种需求非常普遍,所以go提供了range操作符,可以直接用来遍历通过channel传递的数据,在结束后自动跳出

channel又分为buffered/unbuffered两种,以下分别介绍这两种channel

Unbuffered Channels

  1. 简单来说就是同步的,针对某一次发送,接收方没有确认接收的话,发送方将会被阻塞,对接收方也是一样,当尝试接收的时候,接收反进程将会一直等待到收到数据为止。

  2. 其中go确保接收方的接收操作早于发送方被唤醒

  3. 一般来说我们使用channel是为了传递信息,但unbuffered channel自身的同步特性使得我们可以以一种类似于同步锁的方式来使用channel,此时传递的数据其实不包含任何信息,数据类型常常选择一些简单的类型,如int

Buffered Channels

  1. 基本形式

    1
    2
    3
    ch = make(chan int) // unbuffered channel
    ch = make(chan int, 0) // unbufferd channel
    ch = make(chan int, 3) // bufferd channel with capacity 3

    简单来说就是当缓冲区为空的时候,再次尝试接收,接收者会被阻塞,当缓冲区满了,再次尝试发送,发送者会被阻塞,其实很像操作系统中学习的信号灯

  2. 对buffered channel 可以使用len函数,返回缓冲区中元素的个数,同理可以使用cap获得缓冲区的大小

Unidirectional Channel Types

单项的channel。当函数接收channel作为参数的时候,往往同一个channel参数只会使用接收/发送操作,也即在该函数中,channel是单向的。
用法如下:

1
func squarer(out chan<- int, in <-chan int)

其中out只能用于输出(向channel输入),in只能作为输入(从channel中获得),参数的形式其实已经揭示了其用法,out chan <- int就意味着对于out只能使用形如out <- someValue的操作

Looping in Parallel

Example: Concurrent Web Crawler

Multiplexing with select

Example: Concurrent Directory Traversal

Cancellation

Example: Chat Server

以上几章的内容偏向于实践,包含大量代码,建议直接查看原文
其中Cancellation一节主要讲的是可以利用channel的close操作所带来的间接的广播效果(所有使用该channel的函数可以通过receive操作来获取知晓该channel被关闭,进而实现某种意义上的通知),来实现对所有的gorountine的取消(对应的gorountine内部需要编写对应的相应代码)