go并发模型研究

go 实现的是 CSP (Communicating Sequential Process,通讯顺序进程) 模型

CSP

CSP (Communicating Sequential Process,通讯顺序进程) 模型是上个世纪七十年代提出的,不同于传统的多线程通过共享内存来通信CSP 讲究的是 以通信的方式来共享内存。用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。 CSP 中channel是第一类对象,它不关注发送消息的实体,而关注与发送消息时使用的channel

Golang 就是借用 CSP 模型的一些概念为之实现并发进行理论支持,其实从实际上出发,go 语言并没有完全实现了 CSP 模型的所有理论,仅仅是借用了 processchannel这两个概念。**process是在 go 语言上的表现就是 goroutine 是实际并发执行的实体,每个实体之间是通过channel通讯来实现数据共享**。

chanel

Golang 中channel是被单独创建并且可以在goroutine并发实体之间传递,它的通信模式类似于 boss-worker 模式的,一个实体通过将消息发送到channel 中,然后又监听这个 channel 的实体处理,两个实体之间是匿名的,这个就实现实体中间的解耦,其中 channel 是同步的,一个消息被发送到 channel 中,最终是一定要被另外的实体消费掉的,在实现原理上其实类似一个阻塞的消息队列。

goroutine

Goroutine是 Golang 实际并发执行的实体,它底层是使用协程(coroutine)实现并发,coroutine是一种运行在用户态的用户线程,类似于greenthread,go 底层选择使用coroutine的出发点是因为,它具有以下特点:

  • 用户空间 避免了内核态和用户态的切换导致的成本。
  • 可以由语言和框架层进行调度。
  • 更小的栈空间允许创建大量的实例。

更多请看 →线程/协程详解

Goroutine 是异步执行的

有的时候为了防止在结束 main 函数的时候结束掉Goroutine,所以需要同步等待,可以使用一下三种方式:

  • channel

    1
    2
    ch := make(chan int)    无缓冲的channel由于没有缓冲发送和接收需要同步.
    ch := make(chan int, 2) 有缓冲channel不要求发送和接收操作同步.

    例子:

    1
    2
    3
    4
    5
    6
    7
     func main() {
    ch := make(chan struct{})
    go func() { fmt.Println("start working")
    time.Sleep(time.Second * 1)
    ch <- struct{}{} }()
    <-ch //会阻塞直到协程完成
    }
  • sync.WaitGroup

    在 sync 包中,提供了 WaitGroup ,它会等待它收集的所有 goroutine 任务全部完成。在 WaitGroup 里主要有三个方法:
    Add, 可以添加或减少 goroutine 的数量.
    Done, 相当于 Add(-1).
    Wait, 执行后会堵塞主线程,直到 WaitGroup 里的值减至 0.

    例子:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    func main() {
    var wg sync.WaitGroup
    var urls = []string{"http://www.golang.org/", "http://www.google.com/",}
    for _, url := range urls {
    wg.Add(1)
    go func(url string) { defer wg.Done()
    http.Get(url) }(url)
    }
    wg.Wait()
    }
  • context

    context 包主要是用来处理多个 goroutine 之间共享数据,及多个 goroutine 的管理。context 包的核心是 struct Context,接口声明如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    // A Context carries a deadline, cancelation signal, and request-scoped values
    // across API boundaries. Its methods are safe for simultaneous use by multiple
    // goroutines.
    type Context interface {
    // Done 返回一个只能接受数据的channel类型,当该context关闭或者超时时间到了的时候,
    //该channel就会有一个取消信号
    Done() <-chan struct{}

    // Err 在Done() 之后,返回context 取消的原因。
    Err() error

    // Deadline 设置该context cancel的时间点
    Deadline() (deadline time.Time, ok bool)

    // Value 允许 Context 对象携带request作用域的数据,该数据必须是线程安全的。
    Value(key interface{}) interface{}
    }

    Context 对象是线程安全的,你可以把一个 Context 对象传递给任意个数的 gorotuine,对它执行 取消 操作时,所有 goroutine 都会接收到取消信号。

面试题

双协程交替打印 1-10

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 双协程交替打印1-10
package main

import "fmt"

func main() {
a := make(chan int)
exit := make(chan bool)
go func() {
for i := 2; i <= 10; i += 2 {
fmt.Println("A:", <-a) //等待打印
a <- i //传入,下面的协程接受
}
}()
go func() {
defer close(exit)
for i := 1; i <= 9; i += 2 {
a <- i //传入,上面的协程接受
fmt.Println("B:", <-a) //等待打印
}
}()
<-exit
}
您的支持将鼓励我继续创作!