Golang 高并发 | 青训营笔记

134 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 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 中同步进行,所以又称其为同步通道.

image-20230116230856278.png

实例

channel 是并发安全的,带缓冲的channel可以解决消费效率问题.

func CalSquare() {
     src := make(chan int)
     dest := make(chan int3)
 
    // 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 := 0i <= 100i++ {
       lock.Lock()
       x++
       locl.Unlock()
    }
}

func main() {
    for i := 0i <= 10i++ {
       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 中高并发下应避免对内存进行操作,操作前应对其加锁,否则可能出现不可预料的后果。