学习笔记-LEC2-Go 语言工程实践 | 青训营

113 阅读4分钟

LEC2-《Go 语言工程实践》

并发编程

协程(goroutine

轻量级的用户级的线程

Go语言中的协程(Goroutine)是一种轻量级的线程,用于在程序中并发执行任务。协程是Go语言中并发编程的基本单位,与传统的操作系统线程不同,协程由Go运行时(runtime)管理,可以在单个线程上同时运行多个协程。这使得Go能够高效地处理大量的并发任务,而不会消耗过多的系统资源。

协程的主要特点包括:

  1. 轻量级: 创建一个协程的开销很小,因此可以创建数千甚至数百万个协程,而不会造成显著的性能损失。
  2. 并发性: 多个协程可以在同一时间执行,它们之间通过通信来共享数据和协调任务。这种并发性使得在Go程序中可以更自然地编写高效的并发代码。
  3. 通信: 协程之间的通信是通过通道(Channel)来完成的。通道是一种特殊的数据结构,允许协程之间传递数据,实现同步和协作。
  4. 调度: Go运行时具有自己的调度器,可以在多个协程之间进行切换,以实现并发执行。这个调度器基于一种称为"GOMAXPROCS"的设置来控制同时执行的协程数量。

创建一个协程非常简单,只需在函数调用前加上关键字"go"即可

很好理解,字面意思

通信共享一块内存 vs 共享一块内存来互相通信

channel

example

使用通道实现两个子协程之间的通信

     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
             }
         }() // 这个协程是为了向src中写入数据
         go func() {
             defer close(dest)
             for i := range src {
                 dest <- i * i
             }
         }() // 这个协程是为了从src中读取数据并写入到dest中
         for i := range dest {
             //复杂操作
             println(i)
         }
     }

     var (
         x    int64
         lock sync.Mutex
     )
     ​
     func addWithLock() {
         for i := 0; i < 20000; i++ {
             lock.Lock()
             x += 1
             lock.Unlock()
         }
     }
     func addWithoutLock() {
         for i := 0; i < 20000; 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)
     }

依赖管理

SDK

SDK是软件开发工具包(Software Development Kit)的缩写。它是一组软件开发工具和资源的集合,旨在帮助开发人员在特定平台、操作系统或框架上创建应用程序、插件或其他软件。SDK通常包含一系列API(应用程序编程接口)、示例代码、文档和工具,以便开发者能够更轻松地构建应用程序并与特定平台进行交互。

gopath -> vendor -> module

Go Module

incompatible

后来才引入规则需要在主版本2+模块路径增加/vN的后缀

所以一开始没有按照要求的依赖就加上incompatible的后缀进行标识

GOPROXY变量

其实在LEC1中有设置过

GOPROXY 是一个环境变量,用于设置 Go 语言模块(module)下载的代理服务器。在 Go 语言中,模块是用于组织、版本控制和共享代码的一种机制。GOPROXY 的作用是指定一个代理服务器,用于下载和缓存模块,从而加速模块的获取过程。

通过设置 GOPROXY,可以控制从哪个代理服务器获取模块。常见的代理服务器包括 Go 官方的代理、私有代理服务器以及国内的镜像站点。

测试

单元测试

testing.M的用法(简单了解,非必需

覆盖率 加上--cover参数

这个cover只是简单显示下行数覆盖而已

比如

2/3 = 66.7%

Mock

稳定 是指相互隔离,能在任何时间,任何环境,运行测试。

幂等 是指每一 次测试运行都应该产生与之 前一样的

例如只测试processFiestLine()

readFirstLine()则使用一个固定值返回

这样就能消除对本地文件的依赖

本例使用的是monkey的包

基准测试(Benchmark)

ns/op 应该就是ns/每个操作

实践

实践过程

首先获取gin包

 go get gopkg.in/gin-gonic/gin.v1@v1.3.0

Topic和Post对象

parent_id就是帖子所在话题的id

索引

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

map: int64 -> *Topic

初始化

 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
 }

关键步骤如下: 打开文件 将json字符串转换为Topic对象,使用Unmarshal()函数 make()函数创建一个临时map,key为int64类型,value为*Topic类型 将临时map赋值给全局变量topicIndexMap

成功运行

开着代理的情况下

使用http://127.0.0.1:8080/community/page/get/2能get到

但是用http://0.0.0.0:8080/community/page/get/2就会502Bad Gateway

  • 127.0.0.1 是本地回环地址(localhost),它指向本机的网络接口。当您访问 http://127.0.0.1:8080/community/page/get/2 时,请求是直接发往本机的 Web 服务器,而不需要经过代理。
  • 0.0.0.0 是一个特殊的 IP 地址,表示所有可用的 IP 地址或网络接口。在代理服务器上,当您访问 http://0.0.0.0:8080/community/page/get/2 时,代理服务器会尝试将请求转发到其他网络接口或上游服务器。但由于代理配置或其他原因,该请求无法成功转发到上游服务器,导致出现 502 Bad Gateway 错误。

当您关闭代理并使用 0.0.0.0 地址时能够成功,很可能是因为关闭代理后,0.0.0.0 地址被解释为本地主机地址,而不是在代理服务器中作为特殊的监听地址。