从三体质子锁定地球科技 简析Golang的并发
好吧,我承认这个标题是骗你进来的,那么来都来了,看完再走吧。
——
通常情况下,我们都会觉得golang有着不错的性能。那么这个不错的性能是如何得出的呢?我觉得大概有以下几点
-
静态语言VS动态语言
静态语言通常有着比解释型语言更好的性能. 静态语言需要预先编译成二进制机器语言,执行效率自然比解释型语言的解释器边解释边执行效率要高出不少.
所以,单从执行效率上看,C/C++/C#/Java/Golang 等静态要比 Python/Javascript/Ruby等 动态语言要高出不少
-
GC(Garbage Collection)
相比Java的虚拟机JVM或C# 的.NET Framework 提供的垃圾回收机制,Golang有着更高的效率.
详细上,Golang的GC咱没做过仔细研究,.NET 的GC有着引用计数以及三代的垃圾处理机制,实际上,微软在.net core里也做了一定的优化,但总归来说,Golang的GC更加的轻量,执行效率也更高(没有虚拟机呀).
-
并发VS并行
并发:同一时刻,通过调度,来回切换交替运行多个任务,给人的感觉是同时运行的
并行:同一时刻,多个任务同时在运行.
回到标题,在科幻小说<<三体>>中,三体星人通过发送到地球的一个高维展开过的质子就能搅乱地球的所有粒子对撞机的规律, 那么,三体的质子应该是并发来处理的而不是并行的来执行干扰工作.
并发通过切换CPU运行时间片,能达到非常高的执行效率。尤其是在IO密集的计算中.
Golang 使用goroutine 来支持并发, goroutine说白了其实就是协程,但是它比线程轻量。线程的建立需要较多的资源消耗,但是, 执行goroutine只需极少的栈内存。
Golang中的并发
虽然并发并非golang所特有, 比如python就有对协程的支持。但是就便捷性上来讲,Golang从语言层面就支持了并发,使用起来也更加方便.
goroutine通过在函数前加 go关键字实现, 一个官方的例子.
package main
import (
"fmt"
"time"
)
func say(s string) {
for i := 0; i < 5; i++ {
time.Sleep(time.Millisecond * 10)
fmt.Println(s)
}
}
func main() {
go say("world")
say("hello")
}
执行结果:
hello
world
hello
world
world
hello
world
hello
world
hello
Channel
goroutine的函数运行在相同的地址空间,那么就必须做好内存同步工作。相比其它语言的多线程同步锁,golang 使用channel 来进行数据通信, 显得非常的优秀和高效。 channel 可以发送也可以接受channel 类型的值. 我们可以使用make来定义一个channel.
var ch_i = make(chan int)
var ch_s = make(chan string)
var ch_st = make(chan struct{})
举个栗子。
我们要计算一个数组所有元素的累加和, 我们可以把相应的数组拆成若干个goroutine并发执行.
package main
import "fmt"
func sum(a []int, c chan int) {
total := 0
for _, v := range a {
total += v
}
c <- total
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:2], c)
go sum(a[2:4], c)
go sum(a[4:], c)
x, y, z := <-c, <-c, <-c
fmt.Println(x, y, z, x+y+z)
}
执行结果:
4 -1 9 12
三个协程并发计算结果并通过channel 来传递数据.
通过 c <- total, 把计算结果传递给channel, 通过 x, y, z := <-c, <-c, <-c 来接受channel的值.
带缓存的channel
默认的channel是不带缓存的,但我们也可以创建带缓存的channel. 在channel创建的时候可以指定缓存大小。
在缓存大小范围内,就可以一直往channel里塞。直到塞满,阻塞。再直到有人从channel取出,释放缓存。
举个栗子。
我们定义一个带缓存的channel,用来存储斐波那契数组.
package main
import "fmt"
func fibonacci(n int, c chan int) {
x, y := 1, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
}
func main() {
c := make(chan int, 5)
go fibonacci(cap(c), c)
x, y, z, a, b := <-c, <-c, <-c, <-c, <-c
fmt.Println(x, y, z, a, b)
}
运行结果:
1 1 2 3 5
遍历/关闭 channel
上面的例子略显丑,我们可以通过range 函数来遍历缓存里的值.
package main
import "fmt"
func fibonacci(n int, c chan int) {
x, y := 1, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
运行结果:
1
1
2
3
5
8
13
21
34
55
fatal error: all goroutines are asleep - deadlock!
goroutine 1 [chan receive]:
main.main()
C:/Users/trump/go/src/github.com/goroutine-test/main.go:16 +0xb9
系统报了一个错误,意思是说所有的goroutines 都没了。原因就是,range 函数在channel 没有关闭的时候会一直运行,10个读完了以后,没有新的数值进来,它就死了。
解决办法:
在fibonacci函数中关闭通道。
package main
import "fmt"
func fibonacci(n int, c chan int) {
x, y := 1, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x+y
}
close(c)
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)
for i := range c {
fmt.Println(i)
}
}
总结
以上就是个人对Golang 并发的一些粗浅理解。
虽然您可能被骗了,但如果文章对你有所帮助,也是值了。谢谢。