课程学习笔记——Go语言进阶 | 青训营

55 阅读3分钟

语言进阶

Go可以充分发挥多核的优势,高效运行

线程:内核态,比较重量级

协程:用户态,线程可以跑多个协程,比较轻量

Goroutine

快速打印:

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

最后是使用time.sleep进行阻塞,防止在协程未运行结束前主线程先运行结束了。

Channel

协程通过通信来共享内存

func CalSquare() {
    src := make(chan int)
    data := 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)
    }
}

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

WaitGroup并发同步

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

依赖管理

  • GOPATH:环境变量,项目代码直接依赖src下的代码,go get下载最新的包到src目录下
  • Go Vendor:增加vendor文件,存放依赖包的副本,优先从vendor文件里面查找,但是仍然无法控制依赖的版本
  • Go Module:go.mod:依赖管理基本单元、原生库、单元依赖

测试

单元测试

  • 所有测试文件以_test.go结尾
  • func TestXxx(*testing.T)
  • 初始化逻辑放到TestMain中
func HelloTom() string { 
    return "Tom"
} 

func TestHelloTom(t *testing.T) { 
    output := HelloTom() 
    expectOutput := "Tom" 
    assert.Equal(t, expectOutput, output)
}

添加–cover参数可以评价测试代码的覆盖率

Mock测试

一些函数对本地的数据库、文件等有强依赖,在测试的同时找到这些依赖要求过高

可以使用Mock进行测试,在函数执行的时候替换成另外一个函数(打桩),从而规避掉对本地其他的强依赖

func ReadFirstLine() string { 
    open, err := os.Open("log")
    defer open.Close() 
    if err != nil { 
    return "" 
    } 
    scanner := bufio.NewScanner(open) 
    for scanner.Scan() { 
    return scanner.Text() 
    } 
    return "" 
} 

func ProcessFirstLine() string { 
    line := ReadFirstLine()
    destLine := strings.ReplaceAll(line, "11", "00")
    return destLine
} 

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

基准测试

对函数的运行时间进行测试:go test -bench=.

var ServerIndex [10]int 
func InitServerIndex() { 
    for i := 0; i < 10; i++ { 
        ServerIndex[i] = i+100 
    } 
} 

func Select() int { 
    return ServerIndex[rand.Intn(10)] 
} 

func FastSelect() int { 
    return ServerIndex[fastrand.Intn(10)]
} 

func BenchmarkSelect(b *testing.B) { 
    InitServerIndex() 
    b.ResetTimer() 
    for i := 0; i < b.N; i++ { 
        Select()
    } 
} 

func BenchmarkSelectParallel(b *testing.B) { 
    InitServerIndex() 
    b.ResetTimer() 
    b.RunParallel(func(pb *testing.PB) { 
        for pb.Next() { 
            Select() 
        } 
    }) 
} 

func BenchmarkFastSelectParallel(b *testing.B) { 
    InitServerIndex() 
    b.ResetTimer() 
    b.RunParallel(func(pb *testing.PB) { 
        for pb.Next() { 
            FastSelect() 
        } 
    })
}

项目实战:社区话题页面

需求

  1. 实现一个展示话题(标题,文字描述)和回帖列表的后端http接口;
  2. 本地文件存储数据

分层结构

  1. 数据层:数据Model,处理外部数据的增删改查
  2. 逻辑层:业务Entity,处理核心业务逻辑输出
  3. 视图层:视图View,处理和外部的交互逻辑

组件及技术点

具体逻辑见代码

课后实践

  1. 支持对话题发布回帖。
  2. 回帖id生成需要保证不重复、唯一性。
  3. 新加回帖追加到本地文件,同时需要更新索引,注意Map的并发安全问题