引言

Go语言的高并发是go语言引以为傲的一点,也是其它语言爱好者感觉到好奇并想要有一定了解的方面。

首先,Go语言在语言设计上就添加了关于开启并发的关键字“go”,使得在程序员编程方面实现并发特别简单

其次,有了Channels和goroutines,goroutines定义为协程

1协程本质上是一种用户态线程,不需要操作系统来进行抢占式调度,并且在真正的实现中寄存于线程中,因此系统的开销非常小,可以有小的提高线程任务的并发性,避免多线程多的缺点

2优势巨大,轻量级,可以轻松创建上百万个而不会导致系统资源衰竭,而线程和进程最多也不能超过一万个

3语言标准库提供的所有系统的调用操作(包括同步IO操作),可以让CPU给其它goroutine,让轻量级线程的切换管理不依赖于系统的线程也不依赖与线程和进程,也不依赖于CPU的核心量

Channels

当软件的输出取决于事件发生的时间和顺序时,因为我们无法控制,bug 就会出现。因为我们无法准确控制每个 goroutine 写入结果 map 的时间,两个 goroutines 同一时间写入时程序将非常脆弱。Channels是通道,在协程之间促进信息流通,它是一个数据结构,可以同时接受和发送值,可以协调我们的goroutines来解决数据竞争

  • goroutines 是 Go 的基本并发单元,它让我们可以同时检查多个网站。

  • anonymous functions(匿名函数),我们用它来启动每个检查网站的并发进程。

  • channels,用来组织和控制不同进程之间的交流,使我们能够避免 race condition(竞争条件) 的问题。

  • the race detector(竞争探测器) 帮助我们调试并发代码的问题。

现在,从新一点系统的了解go语言中的高并发

go语言中协程的启动特别简单,仅仅在需要并发的函数前面加上关键字 go 即可

package concurrency

type WebsiteChecker func(string) bool

func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
    results := make(map[string]bool)

    for _, url := range urls {
        go func() {
            results[url] = wc(url)
        }()
    }

    return results
}

结果是这个程序并没有达到比较url的效果,没有任何返回值,原因是WebsiteChecker函数对于goroutines来说太快了

为了使得goroutines能够跟上脚步,我们可以使用sleep让它们完成工作

package concurrency

import "time"

type WebsiteChecker func(string) bool

func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
    results := make(map[string]bool)

    for _, url := range urls {
        go func() {
            results[url] = wc(url)
        }()
    }

    time.Sleep(2 * time.Second)

    return results
}

给了两秒钟的时间让goroutines可以完成它们的工作,但等待协程进行的这个方法并不保险,当我们进行多次测试,两个goroutine完全同时写入results map就能导致fatal error

这就是race condition(竞争条件),当软件的输出取决于事件发生的时间和顺序是,我们没有办法控制,两个goroutines同时写入,使得程序特别脆弱。

但是,go语言就为我们设计了一个方式来解决问题,Channels

package concurrency

type WebsiteChecker func(string) bool
type result struct {
    string
    bool
}

func CheckWebsites(wc WebsiteChecker, urls []string) map[string]bool {
    results := make(map[string]bool)
    resultChannel := make(chan result)

    for _, url := range urls {
        go func(u string) {
            resultChannel <- result{u, wc(u)}
        }(url)
    }

    for i := 0; i < len(urls); i++ {
        result := <-resultChannel
        results[result.string] = result.bool
    }

    return results
}
resultsresultChannelmakechan resultresultresultWebsiteCheckerstringbool
mapwcresultresultChannel<-
for<-
result
resultswc

我们已经将想要加快速度的那部分代码并行化,同时确保不能并发的部分仍然是线性处理。我们使用 channel 在多个进程间通信。