青训营第三期 | Go并发编程

95 阅读3分钟

Go并发编程

并发和并行:

  • 并发是程序在一个核的cpu上运行,切换速度快,看起来像并发一样
  • 并行是多线程程序在多个核cpu上运行

协程和线程:

  • 协程:用户态,轻量级线程,栈KB级别

  • 线程:内核态,线程跑多个协程,栈MB级别

image.png

  • go提倡通过通信共享内存而不是通过共享内存实现通信

缓冲和无缓冲

  • 一般来说,我们要保证顺序的有效性,可以使用无缓冲队列

Go依赖管理

Go Path

image.png

  • GoPath弊端:

项目源码都放在一块,会导致不同项目在调用不同版本的包时,无法实现多版本控制

image.png

Go Vendor-弊端

image.png

  • 通过不同项目包内的不同vendor可以去解决包间的依赖冲突问题

  • 如果一个project A依赖projectB 和projectC, 但是他们之间又各自两个版本的Package,这个时候又会出现依赖冲突

image.png

Go Mod

  • 三要素:

  • 1、配置文件,描述依赖 go.mod

  • 2、中心仓库管理依赖库 Proxy

  • 3、本地工具 go get / mod

  • 依赖配置:

  • version:版本号

image.png

  • Indirect:直接依赖和间接依赖

image.png

  • incompatible

image.png

image.png

  • 在编译的时候会默认选择一个最低满足要求的依赖(B)

image.png

  • 为了解决上述问题,就出现了Proxy,通过中心仓库管理依赖库 ,直接循序渐进

image.png

image.png

  • go get

image.png

  • go mod

image.png

测试

  • 常见问题:

image.png

  • 测试种类:

image.png

  • 回归测试:一般是手动通过终端来回归一些固定的主流程场景

  • 集成测试:对系统功能维度做测试

  • 单元测试:在测试开发阶段,对单独的函数、模块做功能验证

  • 测试单元:

image.png

  • 测试规则

image.png

  • 覆盖率:通过测试的代码行数 / 总的代码行数
go test xx_test.go xx.go --cover

image.png

image.png

  • Tip:

image.png

  • 稳定:单元测试能够相互隔离
  • 幂等:多次查询得到的结果一致

解决方案:

image.png

image.png

  • 由于本地文件(修改、删除)会影响到我们的最终结果

image.png

  • 我们通过使用Monkey打桩可以减少对于...的依赖

项目需求:

image.png

需求用例:

image.png

ER图的划分:

image.png

分层结构:

image.png

image.png

项目搭建:

  • 搭建流程:

  • 需求描述

  • 需求用例---->数据库实体

  • ER图的构建以及划分

  • 项目分层模型

  • 项目组件的确定--->技术栈

  • 数据库的建立

  • 服务实体

  • 参数校验(判断某些字段是否有效)

  • 数据准备 (从仓库/数据库中获取数据)

  • 组装实体 (将拿到的数据输出)

image.png

  • Repositor:

image.png

image.png

初始化话题数据索引:

image.png

高并发下只执行一行代码,:

image.png

Service:

image.png

image.png

由于话题和回帖之间是没有依赖的,所以可以并行处理:

image.png

控制器:

image.png

处理Router:

image.png

执行:

image.png image.png

收获:

  • 在查询数据的时候,一般我们都全扫描,但是效率低下。 我们可以用map来实现内存索引,利用文件元数据初始化全局内存索引, 可以实现O(1)的时间复杂度操作

  • 在创建实体的时候,我们需要使用锁操作,避免被抢占

    var userDao *UserDao
    var userOnce sync.Once
    ​
    func NewUserDaoInstance() *UserDao {
        userOnce.Do(
            func() {
                userDao = &UserDao{}
            })
        return userDao
    }
    
  • 在做项目开发时,要思考流程是否可以并行,通过压榨CPU,降低接口耗时,不要一昧的串行实现浪费多核cpu资源

    type QueryPageInfoFlow struct {
        topicId  int64
        pageInfo *PageInfo
    ​
        topic   *repository.Topic
        posts   []*repository.Post
        userMap map[int64]*repository.User
    }
    ​
    func (f *QueryPageInfoFlow) prepareInfo() error {
        //获取topic信息
        var wg sync.WaitGroup
        wg.Add(2)
        var topicErr, postErr error
        go func() {
            defer wg.Done()
            topic, err := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)
            if err != nil {
                topicErr = err
                return
            }
            f.topic = topic
        }()
        //获取post列表
        go func() {
            defer wg.Done()
            posts, err := repository.NewPostDaoInstance().QueryPostByParentId(f.topicId)
            if err != nil {
                postErr = err
                return
            }
            f.posts = posts
        }()
    

\