Go并发

作者: LightiSnow | 来源:发表于2020-02-05 15:45 被阅读0次

    简介

    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方法来更改。如果程序试图使用更多的线程,就会崩溃。

    • 并行(parallelism)与并发(concurrency)的区别

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

    如果想让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依旧可以从通道接受数据,但是不能在向通道里发送数据。

    相关文章

      网友评论

          本文标题:Go并发

          本文链接:https://www.haomeiwen.com/subject/htwvgctx.html