go语言入门 | 青训营笔记

125 阅读1分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第1篇笔记 测试使用

Go语言入门-工程实践

语言进阶(此笔记为作者根据老师讲述内容,会不断补充)

01.并发VS执行

并发:多线程在一个核上cpu运行,我们可以根据示意图看得出来同一个cpu处理俩个事务的时候是同一核心不同时间的

并行:多线程程序在多个核的cpu上运行,特点:不同核心相同时间

1.1 Goroutine

概念:线程、协程

为什么go更适合高并发场景?

  • 线程:内核态 轻量级协程 栈MB级别

  • 协程:用户态 一个线程内部多个协程 栈KB级别 其创建核调度由Go语言负责(可以达上万个)

例子:快速打印多个不同的字符串

  • 快速:开多个协程去打印

代码:

func hello(i int) {
   println("hello :" + fmt.Sprint(i))
}

func HelloGoRoutine() {

   for i := 0; i < 5; i++ {
      go func(j int) {
         hello(j)
         //i应该是输入的传参
      }(i)
   }
   /*暴力阻塞 保证协程执行完 主线程不退出*/
   time.Sleep(time.Second)

}
func main() {
   HelloGoRoutine()
}

结果:

hello :3 hello :2 hello :4 hello :0 hello :1

  • 分析:我们可以通过结果的乱序输出来得出这个是一个并行的go语言输出

1.2 CSP

通过通讯共享内存

示意图

​ --------------------------》Gorountine2

Gorountine 》通道channel

​ --------------------------》Gorountine3

通过共享内存来实现通信

示意图

​ -------------- 《 Gorountine2

Gorountine 》临界区

​ --------------《 Gorountine3

1.3 Channel

make(chan 元素类型,[缓冲大小])

  • 无缓冲通道 make(chan int)

​ 同步通道:Gorountinne同步化

  • 有缓冲通道 make(chan int,2)

    生产销配模型:--oo--> (o指代缓冲点<货架> 如果货架内部有货物 占满后需要等前面的被拿走后,下一个货物才能进入货架)

例子:

描述:模仿生产和消费速度不匹配中通过channel 来解决这里面的问题

func CaSquare() {
   src := make(chan int)
   //有缓冲的通道 解决A线程生产过快 而消费者消费较慢 影响生产者的执行效率
   dest := make(chan int, 3)
   //A子协程
   go func() {
      defer close(src)
      //生产i 并发送到 src
      for i := 0; i < 10; i++ {
         src <- i
      }
   }()
   //B子协程
   go func() {
      //延迟的自由关闭 defer
      defer close(dest)
      for i := range src {
         dest <- i * i
      }
   }()
   for i := range dest {
      //复杂操作
      println(i)
   }
}
func main() {
   CaSquare()
}

1.4 并发安全 Lock (Sync包下)

对变量进行2000次加锁操作,五个协程并发执行

枷锁

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()
}

结果

测试到第三遍终于出错了

WithoutLock: 9200 WithLock: 10000

通过以上结果,我们可以发现,没加锁的way如果在并发的情况下很有可能会出现错误的结果,因此我们在并发情况下要使用Lock来做并发安全的读写操作

枷锁:对临界区的一个控制 保证并发安全

1.5 WaitGroup(Sync包下)

描述:无法确定协程的结束时间,因此无法精确的设置sleep的时间(按老师的来说就是优雅的阻塞0-0)

方法:

  • add(dalta int)计数器+delta
  • Done()计数器-1
  • Wait()阻塞直到计数器为0

例子:

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()
}

func hello(i int) {
   println("hello goroutine : " + fmt.Sprint(i))
}

标题:第三届字节跳动青训营同学必用工具 - 掘金

网址:juejin.cn/post/709112…