这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记。本笔记主要记录go语言在实际开发中的优势与go语言性能强大快速的原因。
1.并发vs并行
从多线程运行的角度上看,
- 并发是指多线程程序在单核cpu上的运行
- 并行是指多线程程序在多核cpu上的运行
go语言实现了并发调度极高的模型,通过高效的调度,可以最大限度利用计算资源发挥多核优势,可以说go语言就是为并发而生的。
2.协程
为了实现高效率的并发运行,在go语言里需要用到协程(goroutine)。协程往往与线程一起理解。
- 线程相比于协程,属于一种比较昂贵的系统资源,属于内核态。它的创建,切换,停止都属于很重的系统操作,需要耗费大量计算资源。它的栈内存属于MB级别。
- 协程更像是轻量级的线程,属于用户态。在go语言内部即可完成对协程的创建和调度。线程可以运行多个协程。它的栈内存属于KB级别。
3.goroutine
快速输出,不要求顺序->需要使用多次协程
package concurrence
import (
"fmt"
"time"
)
func hello(i int) {
println("hello goroutine : " + fmt.Sprint(i))
}
func HelloGoRoutine() {
for i := 0; i < 5; i++ {
go func(j int) {//在调用函数时在函数前加一个go的关键字即可创建协程
hello(j)
}(i)
}
time.Sleep(time.Second)
}
4.channel与sync
对于go语言而言,一般通过通信共享内存。go程序内部为若干协程,协程的通信方式大多以使用线程间通信实现。
- channel是一个用来传递数据的数据结构,可用于两个 goroutine 之间通过传递一个指定类型的值来同步运行和通讯,利用“<- ”指定通道的方向,发送或接收。
package concurrence
func CalSquare() {
src := make(chan int)//创建无缓冲通道的channel
dest := make(chan int, 3)//创建有缓冲通道的channel
go func() {
defer close(src)//关闭通道
for i := 0; i < 10; i++ {
src <- i
}//
}()
go func() {
defer close(dest)
for i := range src {
dest <- i * i
}
}()
for i := range dest {
//复杂操作
println(i)
}
}
- sync(锁)可以进行多个任务同步,避免竞态的发生,保证并发环境中的任务完成。
package concurrence
import (
"sync"
"time"
)
var (
x int64
lock sync.Mutex
)
func addWithLock() {
for i := 0; i < 2000; i++ {
lock.Lock()
x += 1
lock.Unlock()
}
}
func addWithoutLock() {
for i := 0; i < 2000; i++ {
x += 1
}
}
func Add() {
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)
}
func ManyGoWait() {
var wg sync.WaitGroup
wg.Add(5)
for i := 0; i < 5; i++ {
go func(j int) {
defer wg.Done()
hello(j)
}(i)
}
wg.Wait()
}
总结
go语言的并发在实际开发中在速度方面非常有优势,但由于并发无顺序可言,还需要与其他功能一起使用才能达到目的。
参考资料: