这是我参加「第五届青训营」伴学笔记创造活动的第 4 天
前语
本文从并发编程的视角来了解Go高性能的本质、为何Go的运转能够如此之快,首要涉及到知识点:并发、并行、协程、CSP模型、Channel、Mutex、WaitGroup等。
并发编程
并发VS并行
并发是指多个线程在同一个CPU上运转,首要是经过时刻片的切换看起来像多个程序在一起运转,只不过CPU时刻片的切换非常迅速,我们感知不到。
并行是指多个线程在多个CPU上运转,多个线程在同一时刻一起运转而不是时刻片的切换。
Go能够充分发挥多核CPU的优势,高效运转。
协程
在Go完结高并发的机制里,还有一个重要的概念——协程Goroutine,协程也叫做轻量级线程。
线程在创立时需求消耗必定的体系资源,线程归于内核态,线程的创立、切换、停止都归于比较重量级的体系操作,占用体系栈资源归于MB等级。
协程是归于用户态的,归于轻量级线程,协程的创立、切换、毁掉都是由Go言语本身完结,比线程消耗的资源少的多,占用体系栈资源归于KB等级。
一个线程里能够一起履行多个协程,Go能够一起创立上万等级的协程,也是Go支持高并发原因之一。
代码示例
Go创立协程非常简略,只需求在调用的函数前加一个go关键字即可。
import (
"fmt"
"time"
)
func hello(i int) {
println("hello world : " + fmt.Sprint(i))
}
func main() {
for i := 0; i < 5; i++ {
// 敞开协程
go hello(i)
}
// 等协程履行结束后,主线程再结束
time.Sleep(time.Second)
}
CSP模型
CSP(Communicating Sequential Process)通讯顺序进程、交谈循序程序,也被译为交流音讯的循序程序,它是一种用来描述并发行体系之间进行交互的模型。
CSP最大的长处便是灵活,但也很简单出现死锁。
Go提倡经过通讯同享内容而不是经过同享内存而完结通讯。
通道
经过通讯同享内存涉及到另一个概念——通道Channel。
Go能够运用Channel控制子协程,在创立协程时需求创立同样数量的Channel。
每个通道只允许交流指定类型的数据,在Go中运用chan关键字来声明一个通道,运用 close函数来关闭通道。
经过操作符<-来指定通道的方向,完结发送或接收。
通道的创立
make(chan 元素类型, [缓冲巨细])
通道分类:
- 无缓冲通道 make(chan int)
- 有缓冲通道 make(chan int, 2)
无缓冲通道也被称为同步通道。
有缓冲通道也是一个出产-消费模型。
代码示例
下面经过一个代码示例,看一下通道的详细运用,示例经过协程和通道完结输出一个数的平方功用。
代码功用逻辑如下:
- A子协程发送0 ~ 9数字
- B子协程核算输入数字的平方
- 主协程输出最终的平方数
func main() {
src := make(chan int)
dest := make(chan int, 3)
// A子协程
go func() {
defer close(src)
for i := 0; i < 10; i++ {
// 将数字发送到channel
src <- i
}
}()
// B子协程
go func() {
defer close(dest)
for i := range src {
// 将核算好的数字发送到channel
dest <- i * i
}
}()
for i := range dest {
println(i)
}
}
并发安全Lock
Go也有相似Java的锁机制,这种机制是经过同享内存来完结通讯的。
Mutex
Go加锁能够运用Mutex来完结,经过加锁能够完结多个协程在同一时刻只要获取到锁的协程来运转,其他协程只能等候锁的释放,Mutex是一种互斥锁。
下面经过一个比如来看下多个协程对一个数字的相加,经过加锁与不加锁来看下两者的差异。
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x++
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x++
}
}
func main() {
x = 0
for i := 0; i < 5; i++ {
go addWithoutLock()
}
time.Sleep(time.Second)
println("withoutLock:", x)
x = 0
for i := 0; i < 5; i++ {
go addWithLock()
}
time.Sleep(time.Second)
println("withLock:", x)
}
运转代码输出成果为:
withoutLock: 8113
withLock: 10000
其中withoutLock的成果每次运转成果或许不同,能够看出在不加锁的情况下,得出的成果是不满足预期成果的,也便是出现了内存不安全的资源竞争。
WaitGroup
Go中的 WaitGroup 是一个计数信号量,WaitGroup 和 Java 中的 CyclicBarrier、CountDownLatch 非常相似,能够用来记载并维护运转的 goroutine。如果 WaitGroup的值大于 0,Wait 办法就会堵塞,常用来完结并发编程时的同步操作。
WaitGroup有3个办法,Add、Done、Wait。
运用办法:当敞开协程时调用Add办法添加一个计数,完结时调用Done办法减去一个计数,Wait会一向堵塞直到WaitGroup的值为 0。
下面运用WaitGroup来完结一个等候所有协程履行完的比如:
func main() {
var wg sync.WaitGroup
// 敞开5个计数
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
// 履行完时调用Donw
defer wg.Done()
hello(j)
}(i)
}
// 等候所有协程履行完
wg.Wait()
}
总结
本文首要涉及到知识点:并发、并行、协程、CSP模型、Channel、Mutex、WaitGroup等。
引用
Go 言语进阶与依靠办理




