引言
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)
}()
}
(2 * )
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[] =
}
return results
}
除了 results
map 之外,我们现在还有一个 resultChannel
的变量,同样使用 make
方法创建。chan result
是 channel 类型的 —— result
的 channel。新类型的 result
是将 WebsiteChecker
的返回值与正在检查的 url 相关联 —— 它是一个 string
和 bool
的结构。因为我们不需要任何一个要命名的值,它们中的每一个在结构中都是匿名的;这在很难知道用什么命名值的时候可能很有用。
现在,当我们迭代 urls 时,不是直接写入 map
,而是使用 send statement 将每个调用 wc
的 result
结构体发送到 resultChannel
。这使用 <-
操作符,channel 放在左边,值放在右边:
下一个 for
循环为每个 url 迭代一次。 我们在内部使用 receive expression,它将从通道接收到的值分配给变量。这也使用 <-
操作符,但现在两个操作数颠倒过来:现在 channel 在右边,我们指定的变量在左边:
然后我们使用接收到的 result
更新 map。
通过将结果发送到通道,我们可以控制每次写入 results
map 的时间,确保每次写入一个结果。虽然 wc
的每个调用都发送给结果通道,但是它们在其自己的进程内并行发生,因为我们将结果通道中的值与接收表达式一起逐个处理一个结果。
我们已经将想要加快速度的那部分代码并行化,同时确保不能并发的部分仍然是线性处理。我们使用 channel 在多个进程间通信。