这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天
本节课重点
本节课主要讲述了 Golang 高并发实现与可能遇到的并发安全问题。
详细知识点
主要涵括了以下知识点
- 协程 - 轻量级线程
- Channel 协程中的通信
- 并发安全
- 加锁
实践例子
协程
Go语言可以简易的使用go关键字开启协程:
func hello(i int) {
fmt.Printf("hello goroutine: %s", i)
}
func main() {
for i := 0; i < 5; i++ {
go func(j int) {
hello(j)
}(i)
}
// 保证子线程执行完之前,主线程不退出
time.Sleep(time.Second)}
}
最终输出结果可能为:
hello goroutine: 2
hello goroutine: 4
hello goroutine: 5
hello goroutine: 0
hello goroutine: 1
这里我们可以发现结果为乱序输出,可以得出 Goroutine 并行运行。
Channel
Golang 提倡通信共享内存,而不是通过内存共享通信。
在 Golang 中,我们使用 channel 共享通信。
channel又可分为 有缓冲通道与无缓冲通道,无缓冲通道 在两个 gouroutine 中同步进行,所以又称其为同步通道.
实例
channel 是并发安全的,带缓冲的channel可以解决消费效率问题.
func CalSquare() {
src := make(chan int)
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)
}
}
从channel的特性可以看出,我们可以简单使用channel来实现一个简易的 消息队列
并发安全 Lock
在实际项目开发中,我们需要考虑并发安全,避免对共享内存进行操作。
我们可以使用 sync.Mutex 对需操作项进行加锁:
var (
x int64
lock sync.Mutex
)
func add() {
for i := 0; i <= 100; i++ {
lock.Lock()
x++
locl.Unlock()
}
}
func main() {
for i := 0; i <= 10; i++ {
go add()
}
time.Sleep(time.Seconds)
}
waitGroup
我们可以使用 waitGroup进行阻塞,来替换本文开头代码示例的time.Sleep()
func main() {
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()
}
总结
Golang 可以很简单的实现高并发模型,Golang 使用协程而不是线程,它相当于一个轻量级的线程。
线程运行为内核态,而协程运行在用户态,Golang 可以很轻松的实现上万协程,这也是为什么 Golang 适合高并发场景。
Golang 中高并发下应避免对内存进行操作,操作前应对其加锁,否则可能出现不可预料的后果。