后端基础班Day2-并发知识分享 | 青训营笔记

111 阅读3分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 2 天

本篇笔记主要分享一些第二天课程的知识和理解吧

Part 1

Go语言所支持的goroutine协程是非常高效率的,当然它的并发也有并发常见的脏数据问题。

比如说Go早期的map并不是并发安全的,而后面慢慢推出的比如 Sync.map 就是在map层上加锁,而后面也有类似ConcurrentHashMap这种分段加锁实现并发安全的map。

当然Go主推的是通过通信分析内存,说到通信就要提到Go中的channel了。

channel分为有缓存和无缓存的channel,分别对应异步和同步。如果使用的是无缓存的channel,一定要注意死锁情况的发生。

一定要注意的是:

给一个nil的channel发送数据,会造成死锁。

从一个nil的channel接收数据,会造成死锁。

给一个已经关闭的channel发送数据,会造成panic错误。

从一个已经关闭的channel接收数据,如果缓冲区为空,则会返回一个零值。

相信根据上述的说法可以发现channel是服从先入先出FIFO原则的。如果说是有缓存的channel,底层应该会通过ring buffer实现。

image.png

例如缓冲区为8的一个channel buffer。recvx指向最早被读取的数据,而sendx指向再次写入时插入的位置。

而如果chan只是需要传递一种信息,推荐使用空结构体。这是因为空结构体不占内存。非常适合做占位符,而且定义空的结构体时,返回的地址都是一样的,不会去占用其他空间。

课程中还有使用waitgroup计数器进行线程同步,可以通过计数器来操控多个goroutine的结束。每一次Add执行,请求计数器v +1,而Done方法执行,等待计数器w -1,而v为0时通过信号量唤醒Wait()。从而执行接下来的代码。常用于阻塞主线程,避免因为主线程提前结束从而导致goroutine工作未完成而被迫结束。

关于并发之类的东西,还是需要了解Go的Sync包,比如适合单例模式的Sync.Once,降低GC压力的Sync.Pool等等。

Part 2

因为接触Go的语言比较早,大概在1.14左右版本,那时候go mod好像还没有在2019版的goland支持,那时候需要进行编程个人一般都是放在GOPATH下,但是这样管理代码有些麻烦,因为对于依赖包而言几乎是都要全部下载在同一个文件夹底下的,个人现在的GOPATH下还是保存当初的样子。

image.png 在GOPATH下的代码通过go get获取之后都会安装到相应文件夹内,这时候会有一些弊端的出现,有时候需要使用的依赖版本问题,需要去修改文件夹名称,比较繁琐。

而在go module之后,在项目下只需要 go mod init 项目名; go mod tidy; 两行代码就可以把依赖打包好,非常方便。

Part 3

分享一个经典问题,开2个goroutine,1个goroutine打印数字,1个goroutine打印字母

输出样例:
1,2,A,B,3,4,C,D,5,6,E,F,7,8,G,H,9,10,I,J,11,12,K,L,13,14,M,N,15,16,O,P,17,18,Q,R,19,20,S,T,21,22,U,V,23,24,W,X,25,26,Y,Z,
package main

import (
   "fmt"
   "sync"
)

// 交替打印数字和字符
var numc = make(chan struct{})
var bytec = make(chan struct{})
var num int = 1
var wg sync.WaitGroup

func main() {
   wg.Add(1)
   go printNum()
   go printByte()
   numc <- struct{}{}
   wg.Wait()
}
func printNum() {
   for {
      select {
      case <-numc:
         fmt.Printf("%d,%d,", num, num+1)
         bytec <- struct{}{}
         //break
      }
   }
}
func printByte() {
   for {
      select {
      case <-bytec:
         fmt.Printf("%c,%c,", 'A'+num-1, 'A'+num)
         num += 2
         if num >= 26 {
            wg.Done()
            return
         }
         numc <- struct{}{}
         //break
      }
   }
}

其中numc和bytec分别传递信息给函数进行打印,然后通过waitgroup进行主线程的阻塞控制等待即可。

在打印数字之后往chan中传递信息启动另一个函数,以此重复执行即可完成需求。

人生苦短,不如go浪一下。