这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
并发编程
1. goroutine
通过go func的方式创建协程
2. CSP
go提倡通过通信共享内存,而不是通过共享内存来实现通信
3. Channel
make(chan 元素类型,[缓冲大小])
无缓冲通道 make(chan int)
有缓冲通道 make(chan int, 2)
4. 并发安全Lock
var x = 0;
var lock = sync.Mutex
func addWithLock(){
lock.lock();
x+=1;
lock.unlock();
}
5. WaitGroup
计时器:开启协程加1,执行结束减1;主协程阻塞直到计数器为0
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()
}
依赖管理
1. GO依赖管理演进
GOPATH->GO Vendor->Go Module
2. GOPATH
弊端:无法实现多版本控制
3. Vendor
弊端:无法控制版本,项目更新后可能出现依赖冲突
4. Go Module
- 后面的实战项目中使用的go module,虽然在项目文件夹中运行命令,但安装的依赖包是在GOPATH下,还可以设置环境变量改变依赖管理方式。
5. 依赖管理三要素
1. 配置文件,描述依赖 go.mod
2. 中心仓库管理依赖库 Proxy
3. 本地工具 go get/mod
6. 依赖配置- go.mod
7. 依赖配置-version
MAJOR是一个大版本,不同MAJOR间代码隔离
MINOR是新增函数或功能,需要做到MAJOR代码兼容
PATCH是bug修复
8. GOPROXY
9. 工具- go get
10. 工具-go mod
测试
两个概念:
幂等: 多次重复运行同一个单元测试,结果是一样的
稳定: 单元测试相互隔离的,任何时间单元测试的任何函数能够独立的运行。依赖外部环境可能受网络等影响不能在任意时间都能独立运行。
项目实战
分层结构
组件工具
课后实践
- 支持发布帖子
- 本地id生成需要保证不重复,唯一性
- Append文件,更新索引,注意Map的并发安全问题
下面从顶至下来看该功能接口的实现:
首先是路由需要增加一个回帖的路由,需要用POST方法,参数是话题id和帖子内容,参数类型是FormData
func main(){
if err := Init("./data/"); err != nil {
os.Exit(-1)
}
r := gin.Default()
r.GET("/community/page/get/:id",func(c *gin.Context) {
topicId := c.Param("id")
data := controller.QueryPageInfo(topicId)
c.JSON(200,data)
})
//发布帖子
r.POST("/community/page/post",func(c *gin.Context) {
parentId := c.PostForm("parentId")
content := c.PostForm("content")
data := controller.AddPost(parentId,content)
c.JSON(200,data)
})
err := r.Run()
if err != nil {
return
}
}
对应controller层方法:
简单的调用service层方法和结果封装
func AddPost(parentId string,content string) *PageData {
err := service.AddPost(parentId,content)
if err != nil {
return &PageData {
Code: -1,
Msg: err.Error(),
}
}
return &PageData{
Code: 0,
Msg: "success",
}
}
再来看service层:
这里没有像课上例子一样增加参数校验,只实现主要功能,将除id以外的其他post属性补全,调用dao层方法添加帖子
func AddPost(parentId string,content string) error {
parentIdInt, err := strconv.ParseInt(parentId,10,64)
if err != nil {
return err
}
post = repository.Post{
ParentId: parentIdInt,
Content: content,
CreateTime: time.Now().Unix(),
}
err = repository.NewPostDaoInstance().AddPost (post)
if err != nil {
return err
}
return nil
}
dao层:
这里需要获取唯一的id,方法是在从文件初始化数据的时候就记录最大的id数,以后每增加一条就加1。利用bufio中的writer来写入文件,这里的postIndexMap是对Map做了线程安全处理的Map,实现方法就是加锁。
func(*PostDao) AddPost(post Post) error {
open, err := os.OpenFile("./data/post",os.O_WRONLY | os.O_CREATE | os.O_APPEND,os.ModeAppend)
if err != nil {
return err
}
defer open.Close()
post.Id=maxPostId+1
maxPostId++
posts,_ := postIndexMap.Get(post.ParentId)
posts = append(posts,&post)
postIndexMap.Set(post.ParentId,posts)
byteText, err := json.Marshal(post)
text := string(byteText[:])
text = "\n"+text
if err != nil {
return err
}
writer := bufio.NewWriter(open)
if _, err := writer.Write([]byte(text)) ; err != nil {
return err
}
writer.Flush()
return nil
}
线程安全map:
这段代码来自知乎
type ConcurrencyMap struct {
sync.RWMutex
concurrencyMap map[int64][]*Post
}
func NewConcurrencyMap()*ConcurrencyMap {
return &ConcurrencyMap{
concurrencyMap: make(map[int64][]*Post),
}
}
func (c *ConcurrencyMap) Get(k int64)([]*Post,bool) {
c.RLock()
defer c.RUnlock()
v, visited := c.concurrencyMap[k]
return v, visited
}
func (c *ConcurrencyMap) Set(k int64, v []*Post) {
c.Lock()
defer c.Unlock()
c.concurrencyMap[k]=v
}
func (c *ConcurrencyMap) Delete(k int64){
c.Lock()
defer c.Unlock()
delete(c.concurrencyMap,k)
}