Go语言上手-工程实践 | 青训营笔记

98 阅读4分钟

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

  1. 并发并行的区别 并行是指多个事件在同一时刻发生,而并发是指多个事件在同一时间段发生。

image.png

  1. 协程与线程区别

协程

协程是一种用户态的轻量级线程,协程的调度完全由用户控制。
从技术的角度来说,“协程就是你可以暂停执行的函数”。协程拥有自己的寄存器上下文和栈。
协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

区别

  1. 一个线程可以多个协程,一个进程也可以单独拥有多个协程。

  2. 线程进程都是同步机制,而协程则是异步。

  3. 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。

  4. 线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。

  5. 协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的CPU资源, 协程是组织好的代码流程, 协程需要线程来承载运行, 线程是协程的资源, 但协程不会直接使用线程, 协程直接利用的是执行器(Interceptor), 执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建新程.。

  6. 线程是协程的资源。协程通过Interceptor来间接使用线程这个资源

3.csp image.png

这句话怎么理解呢

我们首先要了解什么是process和channel。 process 就是Go语言中的 goroutine,每个 goroutine 之间是通过 channel 通讯来实现数据共享。

在锁模式中,一块内存可以被多个线程同时看到,所以叫共享内存。线程之间通过改变内存中的数据来通知其他线程发生了什么,所以是通过共享内存来通信。 而图中这句话怎么理解呢,二者本质都是让同一时刻一个线程操作同一块内存,以保护逻辑的原子性。但是使用共享内存的话在多线程的场景下为了处理竞态,需要加上锁,用起来比较麻烦。而且使用的锁太多,会使得程序的逻辑不好理解,且容易造成死锁,排查问题相当困难。

go语言的channel保证同一时间只有一个goroutine能够访问里面的数据,为开发者提供了一种优雅简单的工具,所以go原生的做法就是使用channle来通信,而不是使用共享内存来通信。

GO语言依赖管理

演进

image.png GOPATH弊端 A项目B项目依赖同一包的不同版本,如果包升级之后会导致A无法正常访问,也就是无法实现package的多版本控制。 Go Vendor 所有依赖包副本形式放在vendor文件下,解决了多个项目需要同一个package的依赖冲突问题。

image.png Go Module 通过go.mod文件管理依赖包版本,通过go get/go mod指令工具管理依赖包。

依赖管理三要素

  1. 配置文件,描述依赖
  2. 中心仓库管理依赖库(proxy)
  3. 本地工具

go.mod里//indirect表示非直接依赖

image.png 本题选B,因为gomod会选择最低的兼容版本

依赖分发-回源

image.png 所以我们用到了Proxy,稳定可靠. gomod相当于java中的maven,它的优点可以参考gomod介绍及优点

测试

分为回归测试,集成测试,单元测试,覆盖率逐渐变大,成本却逐渐降低

测试规则

  • 所有测试文件都以_test.go结尾
  • func TestXxx(*testing.T)
  • 初始化逻辑放到TestMain中

项目实践

分层结构

image.png 数据层 用单例模式创建一个DAO

package repository

import (
	"sync"
)

type Post struct {
	Id         int64  `json:"id"`
	ParentId   int64  `json:"parent_id"`
	Content    string `json:"content"`
	CreateTime int64  `json:"create_time"`
}
type PostDao struct {
}
var (
	postDao *PostDao
	postOnce sync.Once
)
func NewPostDaoInstance() *PostDao {
	postOnce.Do(
		func() {
			postDao = &PostDao{}
		})
	return postDao
}
func (*PostDao) QueryPostsByParentId(parentId int64) []*Post {
	return postIndexMap[parentId]
}

话题,回复采用索引来提高性能.

服务层有三个步骤checkParam(),prepareInfo(),packPageInfo()

func (f *QueryPageInfoFlow) checkParam() error {
	if f.topicId <= 0 {
		return errors.New("topic id must be larger than 0")
	}
	return nil
}

func (f *QueryPageInfoFlow) prepareInfo() error {
	//获取topic信息
	var wg sync.WaitGroup
	wg.Add(2)
	go func() {
		defer wg.Done()
		topic := repository.NewTopicDaoInstance().QueryTopicById(f.topicId)
		f.topic = topic
	}()
	//获取post列表
	go func() {
		defer wg.Done()
		posts := repository.NewPostDaoInstance().QueryPostsByParentId(f.topicId)
		f.posts = posts
	}()
	wg.Wait()
	return nil
}

func (f *QueryPageInfoFlow) packPageInfo() error {
	f.pageInfo = &PageInfo{
		Topic:    f.topic,
		PostList: f.posts,
	}
	return nil
}

最后是视图层,调用service层服务.返回PageInfo

func QueryPageInfo(topicIdStr string) *PageData {
	topicId, err := strconv.ParseInt(topicIdStr, 10, 64)
	if err != nil {
		return &PageData{
			Code: -1,
			Msg:  err.Error(),
		}
	}
	pageInfo, err := service.QueryPageInfo(topicId)
	if err != nil {
		return &PageData{
			Code: -1,
			Msg:  err.Error(),
		}
	}
	return &PageData{
		Code: 0,
		Msg:  "success",
		Data: pageInfo,
	}

}

server.go启动程序,之后在任一终端运行curl命令,即可正确查到信息.

image.png