go工程实践|青训营笔记

95 阅读3分钟

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

go语言进阶

一、并发编程:

1.go语言相较于其他语言更适合并发('快'标签)

首先我们需要了解并发和并行的区别

并发 :多线程程序在一个核上的cup上运行(由于速度较快,不同的线程拿到时间片,运行完后又再次切换,cpu运行速度快,给人的感觉就是同时运行)

并行 :过线程程序在多个核的cpu上运行(多个核工作不相互影响,真同时运行)

go语言则可以充分发挥多核的优势:由于他相较其他高级语言来说,有着自己的获取到线程之后的分配机制(简单来讲就是在go拿到系统分配的线程后,有着自己的独立分配机制,再将系统线程划分到自己的每个进程中--协程)这就是go语言适合高并发的原因

2.go中创建线程

下面是一个展示使用go语言协程的案例

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) {
         hello(j)
      }(i)
   }
   time.Sleep(time.Second) // 这里由于可能子线程在主线程销毁之后才执行完,因此让主线程等待
}

从上面的案例能看出,go语言有着成熟简洁的多线程方式即使用 go func(){}() 这样一个回调函数加 go关键字,即可开启一个Goroutine协程,且由于其占用内存很小,因此在go中开辟百万数据量的协程是一件轻松的事

3.go中的Channel管道

IMG_C2BA4671291E-1.jpeg 根据实际业务来创建有无缓冲的通道

  • 当对于数据量不需要太多时间产生或者消耗,没有必要进行等待时直接使用无缓冲,将数据从源到目的
  • 当数据量较大或者生产耗费较多时间时,则需要选取合适大小的缓冲区来预防数据丢失,阻塞过久等问题

案例

package concurrence

func CalSquare() {
   src := make(chan int) // 源 无缓冲
   dest := make(chan int, 3) // 目的地 有缓冲
   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)
   }
}

既然有高并发,因此就有相应的问题

3.高并发的安全问题(Lock)

由于高并发可能存在操作同一数据的操作,因此在操作时就存在脏读,同时写错问题,解决方法就需要设置互斥形信号量来对不同线程限制访问(同时回降低效率,但是换取正确数据这是有必要的)。

以下案例则展现了不互斥访问,和互斥访问的结果现象

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

运行结果:

WithoutLock: 8621; WithLock: 10000;

二、依赖管理

在之前java的学习中,使用各个框架,以及数据库,以及各个插件,学习过使用maven来管理项目的依赖,同样在go的开发中也有他自己的依赖管理 Go Module(经历过三次迭代 GOPATH -> GO Vendor -> Go Module(go.mod文件)

IMG_9E88979CEB3A-1.jpeg

go中更是进一步改进了依赖的托管(增加一层Proxy代理) IMG_F01CAACDEC0A-1.jpeg

三、测试

类似于java中junit插件,go中也有着自己的测试插件 IMG_038465492007-1.jpeg 从上到下,覆盖率逐层变大,成本却逐层降低

  1. 回归测试:

    类似于开发程序,让测试用户体验总体的整个软件,类似各大游戏的内测环节

  2. 集成测试:

    系统功能为主,测试主要功能

  3. 单元测试:

    开发阶段,每个不同的阶段进行测试

单元测试的规则类似junit

package test

import (
   "github.com/stretchr/testify/assert" // 进行对比判断
   "testing" // 导入测试包
)

func TestHelloTom(t *testing.T) {
   output := HelloTom()
   expectOutput := "Tom"
   assert.Equal(t, expectOutput, output) // expectOutput是预期结果
}

通过导入两个包来对单元进行测试,如果达成结果则完成测试

四、小项目demo(实现帖子以及回帖/评论):

再次巩固后端的分层结构,由于带我们浅尝一下go开发项目,因此没有使用数据库,使用文件来存储数据,并且去掉Client,通过返回的json直接来检验成果 IMG_E3FAC9ED5789-1.jpeg

由于涉及到web的开发,因此这里涉及到框架的使用gin

通过go mod init 对项目实现初始化

通过go get gopkg.in/gin-gonic/gin.v1@v1.3.9 go get指令获取gin插件

代码的实现 通过map作为索引,因为其时间复杂度为 o(1),因此在服务启动时初始化两个索引容器:~ 页面 ~ 页面附属评论

// 数据层的实现
package repository

import (
   "bufio"
   "encoding/json"
   "os"
)

var (
   topicIndexMap map[int64]*Topic
   postIndexMap  map[int64][]*Post
)

func Init(filePath string) error {
   if err := initTopicIndexMap(filePath); err != nil {
      return err
   }
   if err := initPostIndexMap(filePath); err != nil {
      return err
   }
   return nil
}

func initTopicIndexMap(filePath string) error {
   open, err := os.Open(filePath + "topic")
   if err != nil {
      return err
   }
   scanner := bufio.NewScanner(open)
   topicTmpMap := make(map[int64]*Topic)
   for scanner.Scan() {
      text := scanner.Text()
      var topic Topic
      if err := json.Unmarshal([]byte(text), &topic); err != nil {
         return err
      }
      topicTmpMap[topic.Id] = &topic
   }
   topicIndexMap = topicTmpMap
   return nil
}

func initPostIndexMap(filePath string) error {
   open, err := os.Open(filePath + "post")
   if err != nil {
      return err
   }
   scanner := bufio.NewScanner(open)
   postTmpMap := make(map[int64][]*Post)
   for scanner.Scan() {
      text := scanner.Text()
      var post Post
      if err := json.Unmarshal([]byte(text), &post); err != nil {
         return err
      }
      posts, ok := postTmpMap[post.ParentId]
      if !ok {
         postTmpMap[post.ParentId] = []*Post{&post}
         continue
      }
      posts = append(posts, &post)
      postTmpMap[post.ParentId] = posts
   }
   postIndexMap = postTmpMap
   return nil
}