这是我参与「第三届青训营 -后端场」笔记创作活动的第2篇笔记
- 并发并行的区别 并行是指多个事件在同一时刻发生,而并发是指多个事件在同一时间段发生。
- 协程与线程区别
协程
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。
从技术的角度来说,“协程就是你可以暂停执行的函数”。协程拥有自己的寄存器上下文和栈。
协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。
区别
-
一个线程可以多个协程,一个进程也可以单独拥有多个协程。
-
线程进程都是同步机制,而协程则是异步。
-
协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态。
-
线程是抢占式,而协程是非抢占式的,所以需要用户自己释放使用权来切换到其他协程,因此同一时间其实只有一个协程拥有运行权,相当于单线程的能力。
-
协程并不是取代线程, 而且抽象于线程之上, 线程是被分割的CPU资源, 协程是组织好的代码流程, 协程需要线程来承载运行, 线程是协程的资源, 但协程不会直接使用线程, 协程直接利用的是执行器(Interceptor), 执行器可以关联任意线程或线程池, 可以使当前线程, UI线程, 或新建新程.。
-
线程是协程的资源。协程通过Interceptor来间接使用线程这个资源
3.csp
这句话怎么理解呢
我们首先要了解什么是process和channel。 process 就是Go语言中的 goroutine,每个 goroutine 之间是通过 channel 通讯来实现数据共享。
在锁模式中,一块内存可以被多个线程同时看到,所以叫共享内存。线程之间通过改变内存中的数据来通知其他线程发生了什么,所以是通过共享内存来通信。 而图中这句话怎么理解呢,二者本质都是让同一时刻一个线程操作同一块内存,以保护逻辑的原子性。但是使用共享内存的话在多线程的场景下为了处理竞态,需要加上锁,用起来比较麻烦。而且使用的锁太多,会使得程序的逻辑不好理解,且容易造成死锁,排查问题相当困难。
go语言的channel保证同一时间只有一个goroutine能够访问里面的数据,为开发者提供了一种优雅简单的工具,所以go原生的做法就是使用channle来通信,而不是使用共享内存来通信。
GO语言依赖管理
演进
GOPATH弊端
A项目B项目依赖同一包的不同版本,如果包升级之后会导致A无法正常访问,也就是无法实现package的多版本控制。
Go Vendor
所有依赖包副本形式放在vendor文件下,解决了多个项目需要同一个package的依赖冲突问题。
Go Module
通过go.mod文件管理依赖包版本,通过go get/go mod指令工具管理依赖包。
依赖管理三要素
- 配置文件,描述依赖
- 中心仓库管理依赖库(proxy)
- 本地工具
go.mod里//indirect表示非直接依赖
本题选B,因为gomod会选择最低的兼容版本
依赖分发-回源
所以我们用到了Proxy,稳定可靠.
gomod相当于java中的maven,它的优点可以参考gomod介绍及优点
测试
分为回归测试,集成测试,单元测试,覆盖率逐渐变大,成本却逐渐降低
测试规则
- 所有测试文件都以_test.go结尾
- func TestXxx(*testing.T)
- 初始化逻辑放到TestMain中
项目实践
分层结构
数据层
用单例模式创建一个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命令,即可正确查到信息.