Go并发

2020-02-05  本文已影响0人  LightiSnow

简介

Go语言调度器在操作系统之上,将操作系统的线程与语言运行时的逻辑处理器绑定,并在逻辑处理器上运行goroutine。

Go语言的并发同步模型来自一个叫做通信顺序进程(Communicating Sequential Processes,CSP)的范型。CSP是一种消息传递模型。通过在goroutine之间传递数据来传递消息,而不是对数据进行加锁来实现同步访问。

用于在goroutine之间同步和传递数据的关键数据类型叫做通道

在1.5版本上,Go语言的运行时默认会为每个可用的物理处理器分配一个逻辑处理器。

并发与并行

如果创建一个goroutine并准备运行,这个goroutine就会被放到调度器的全局运行队列中。之后,调度器就将这些队列中的goroutine分配给一个逻辑处理器,并放到这个逻辑处理器对应的本地运行队列中。本地运行队列中的goroutine会一直等待知道自己被分配的逻辑处理器执行。

如果一个 goroutine 需要做一个网络 I/O 调用,流程上会有些不一样。在这种情况下,goroutine
会和逻辑处理器分离,并移到集成了网络轮询器的运行时。一旦该轮询器指示某个网络读或者写操作已经就绪,对应的 goroutine 就会重新分配到逻辑处理器上来完成操作。

调度器对可以创建的逻辑处理器的数量没有限制,但语言运行时默认限制每个程序最多创建 10 000 个线程。这个限制值可以通过调用 runtime/debug 包的 SetMaxThreads方法来更改。如果程序试图使用更多的线程,就会崩溃。

并行是让不同的代码片段同时在不同的物理处理器上执行。并行的关键是同时做很多事情,而并发是指同时管理很多事情,这些事情可能只做了一半就被暂停去做别的事情了。

如果想让goroutine并行,必须使用多于一个逻辑处理器。

竞争状态

go build -race可以用来检查竞争状态。

$ go build -race
$ ./listing09.exe
==================
WARNING: DATA RACE
Read at 0x0000006072f8 by goroutine 7:
  main.incCounter()
      E:/code/chapter6/listing09/listing09.go:40 +0x76

Previous write at 0x0000006072f8 by goroutine 6:
  main.incCounter()
      E:/code/chapter6/listing09/listing09.go:49 +0x97

Goroutine 7 (running) created at:
  main.main()
      E:/code/chapter6/listing09/listing09.go:26 +0x90

Goroutine 6 (finished) created at:
  main.main()
      E:/code/chapter6/listing09/listing09.go:25 +0x6f
==================
Final Counter: 4
Found 1 data race(s)

锁住共享资源

atomic.AddInt64(&counter, 1)
atomic.LoadInt64(&counter, 1)
atomic.StoreInt64(&counter, 1)
mutex.Lock()
{
    ...// 代码块,创建临界区,同一时刻,只有一个goroutine可以进入临界区
}
mutex.Unlock()

通道

当一个资源需要在goroutine之间共享时,通道在goroutine之间架起了一个管道,并提供了确保同步交换数据的机制。声明通道时,需要指定将要被共享的数据的类型。可以通过通道共享内置类型、命名类型、结构类型和引用类型的值或指针。

使用内置函数make创建通道,在创建有缓冲通道时,第二个参数用来指明缓冲去的大小

// 无缓冲的整型通道
unbuffered := make(chan int)

// 有缓冲的字符串通道
buffered := make(chan string, 10)

// 通过通道发送一个字符串
buffered <- "Gopher"

// 从通道接受一个字符串
value := <- buffered

接受前没有能力保存任何值,要求发送的goroutine和接受的goroutine同时准备好,才能完成发送和接受的操作。如果两个goroutine没有同时准备好,通道会导致先执行发送或接受操作的goroutine阻塞等待。这种对通道进行发送和接受的行为本身就是同步的。

和无缓冲通道最大的区别是:无缓冲的通道保证进行发送和接受的goroutine会在同一时间进行数据交换;有缓冲的通道没有这种保证。通道关闭后,goroutine依旧可以从通道接受数据,但是不能在向通道里发送数据。

上一篇下一篇

猜你喜欢

热点阅读