Go实战 | 青训营笔记

82 阅读4分钟

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

Day2 Go工程进阶

Go语言进阶

并发 vs并行

并发是实现在单核CPU上的时间片的切分,并发是实现字在多核CPU的配合

Go可以充分发挥多核优势,提高效率

Go routine

协程是用户态(轻量),线程是内核态(昂贵的资源)

go func(...){}()
//开启一个协程

CSP(Computing Sequential Processes)

Go通过通信共享内存(保证收发顺序),但也保留了不太好的通过共享内存来实现通信

Channel

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

  1. 无缓冲通道 make(chan int) —— 收发routine通信时保持同步
  2. 有缓冲通道 make(chan int, 2) —— 阻塞发送,不同步处理

通过合理使用两种通道,可以合理地解决生产与消费速度不一致的问题。

Sync.Lock

如果使用共享内存实现通信,就需要使用Lock来保证并发安全

Lock()方法 :写锁,如果在添加写锁之前已经有其他的读锁和写锁,则Lock就会阻塞直到该锁可用,为确保该锁最终可用,已阻塞的Lock调用会从获得的锁中排除新的读取锁,即写锁权限高于读锁,有写锁时优先进行写锁定。

Unlock()方法 写锁解锁,如果没有进行写锁定,则就会引起一个运行时错误。

Sync.WaitGroup替代time.Sleep()优雅地实现 并发 同步

WaitGroup有三个方法,Add,Done,Wait

当计数器为0时,表明所有 并发 的协程都执行完毕

Go依赖管理

————多文件项目的组织关系

依赖管理的演进

GOPATH

主要有三部分:bin src pkg

项目代码直接依赖于src下的代码,go get直接下载最新版本到src目录下

————问题:无法实现多版本控制

Go Vender

在项目目录下增加一个vendor目录,在项目寻找依赖时优先从vendor搜索

————问题:仍然会依赖于源码,并没有明确的版本概念

Go Mudule

通过go.mod文件管理依赖包版本

version有语义化版本commit伪版本

前者分别是:大版本.兼容更新.bug修复

后者分别是:语义化-时间戳-12位hash码

Indirect A->B->C 如果A->C 就叫间接依赖

Incompatible 表示可能存在冲突的包

依赖分发

可能在Github SVN上托管,但是可能无法保证稳定性、可用性,而且增加第三方压力

PROXY

因此我们引入的代理服务站,缓存版本内容,实现稳定可靠的分发

比如我们配置环境时见到的GOPROXY=”url1, url2, direct“

意思就是优先从url1,然后url2,如果都没有就回到源站下载

go mod常用指令

go mod init//初始化go.mod文件
go mod download//下载依赖包——可用tidy包括
go mod tidy//更新包资源(下载和删除)

Go测试

回归测试 ——集成测试——单元测试(从左到右覆盖率逐层变大,成本逐层降低)

单元测试

  1. 文件后缀:_test.go
  2. 测试函数:func Test(t *testing.T)
  3. 初始化逻辑放在TestMain(m *testing.M)中
  4. 可以自己实现判断逻辑,也可以引入assert包使用
assert.Equal(t, expectOutput, output)
//t为*testing.T类型
  1. 覆盖率是测试质量的基准,如下代码常被使用
go test judgement_test.go judgement.go --cover
//--cover是为了输出覆盖率

Mock测试

使用monkey包,利用打桩来测试,解除一些依赖场景

比如不再需要关注本地文件是否存在

package test

import (
    "bou.ke/monkey"
    "github.com/stretchr/testify/assert"
    "testing"
)

func TestProcessFirstLine(t *testing.T) {
    firstLine := ProcessFirstLine()
    assert.Equal(t, "line00", firstLine)
}

func TestProcessFirstLineWithMock(t *testing.T) {
    monkey.Patch(ReadFirstLine, func() string {//制造虚拟文件场景,不依赖于本地文件
        return "line110"
    })
    defer monkey.Unpatch(ReadFirstLine)
    line := ProcessFirstLine() 
    assert.Equal(t, "line000", line)
}

基准测试

做好对比,然后进行优化

rand()在高并发场景下并不好,可用fastrand()替代

项目实践

ER图(Entity Relationship Diagram)

可以认为是数据的抽象体,用图示的方法把项目涉及到的数据组织起来。

分层结构(常用的结构)

框架——Go Gin 高性能 go web框架

常见的项目实现过程:需求分解——逐层解决——充分测试

课后作业

这里我按照原来文件里的那种方法,进行了ID顺次增大的处理,然后增加帖子后返回包括着ID的结构体

主体代码如下,不过有变量名做了更改(因为一些变量如果需要成为全局变量,需要首字母大写)

    r.POST("/community/page/topic", func(c *gin.Context) {
        defer c.Request.Body.Close()
        var pt repository.Topic
        data, err := ioutil.ReadAll(c.Request.Body)
        if err != nil {
            println(err)
        }
        err = json.Unmarshal(data, &pt)
        if err != nil {
            println(err)
        }
        pt.Id = repository.MaxIndex + 1
        repository.MaxIndex++
        repository.TopicIndexMap[pt.Id] = &pt
        pt.CreateTime = time.Now().UnixNano()
        jstp, _ := json.Marshal(pt)
        string_pt := "\n" + string(jstp)
        open, err := os.OpenFile("./data/topic", os.O_APPEND, 0644)
        if err != nil {
            logrus.Error(err)
            open.Close()
        }
        _, err = open.WriteString(string_pt)
        if err != nil {
            logrus.Error(err)
        }
        if err = open.Close(); err != nil {
            logrus.Error(err)
        }
        c.JSON(200, pt)
    })