这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
第二节课有四个部分,分别学习了go语言的并发编程实现,go语言依赖管理的演变,单元测试的实现,展示话题页面的项目实战。
Go语言并发编程
协程
go语言和其它编程语言类似,都可以通过线程实现并发编程,而go语言还拥有协程(Goroutine)这一功能,协程可以被视为用户态线程,其切换开销远远小于线程,使得go语言更适合于实现高并发的程序。
go语言通过关键字go开启协程,为一个函数创建一个协程运行
go func(){...}()
以上语句会为该函数单独创建一个协程,但是该方式不会有返回值,若需要使用到函数的返回值,可以通过管道等方式传输到主协程。对该函数传递的参数可以写在最后的括号中传入。
需要注意的是,如果父协程在子协程结束之前就退出了,那么子协程也会被强制退出,这时,就需要考虑两个协程之间的同步问题。
WaitGroup
WaitGroup可以用于协程间的同步,在第一篇笔记中已经有介绍,就不赘述了。父协程可以使用WaitGroup在子协程退出之后再退出,避免将子协程强制关闭。
通道
通道channel用于协程间的通信,go语言比较提倡使用通道进行协程间通信,而不去使用共享内存方式,因为共享内存方式会使用到锁机制,在大量协程并发执行时,对锁的争夺可能会导致性能下降。
通道遵循先进先出的原则,保证消息的顺序,同一个时刻只能有一个协程访问通道。通道可以分为两大类:无缓冲通道和有缓冲通道。
无缓冲通道:发送方和接收方必须同时准备好,否则会导致就绪的一方陷入阻塞。
有缓冲通道:有一个在初始化时指定好的缓冲容量,传输数据可以暂时存放在缓冲区内,不要求发送方和接收方同时准备好。但是在以下两种情况下还是会发生阻塞:1.缓冲区为空,但接收方仍在接受,此时接收方被阻塞。2.缓冲区为满,但发送方仍在发送,此时发送方被阻塞。
//该方式创建了一个int类型的无缓冲通道
c := make(chan int)
//该方式创建了一个int类型的大小为5的有缓冲通道
c := make(chan int, 5)
//向通道中传入数据
c <- 2
//从通道中读取数据,ok用来判断是否读取成功
v, ok := <-c
//有缓冲通道也可以使用for range方式读取通道数据
Lock
sync.Lock用于对资源上锁来实现互斥访问,使用lock()和unlock()方法来实现加锁和解锁。
项目实战
本项目主要实现一个社会话题页面的后端,要求如下:
- 话题页面包括标题,文字描述和回帖列表
- 数据使用文件存储
课后作业要求添加以下内容:
- 支持发布帖子
- 帖子的id有唯一性
- 新增内容append在文件末尾,注意更新map索引
首先需要在server.go中新增路由:
r.GET("/community/page/addPost/:postContent/:topicId", func(c *gin.Context) {
postContent := c.Param("postContent")
topicId := c.Param("topicId")
data := cotroller.AddPost(postContent, topicId)
c.JSON(200, data)
})
然后再controller层增加发布帖子的函数:
func AddPost(postContext, topicIdStr string) *PageData {
topicId, err := strconv.ParseInt(topicIdStr, 10, 64)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
err = service.AddPost(postContext, topicId)
if err != nil {
return &PageData{
Code: -1,
Msg: err.Error(),
}
}
return &PageData{
Code: 0,
Msg: "success",
}
}
在service层新增函数:
func AddPost(postContext string, topicId int64) error {
return repository.NewPostDaoInstance().AddPost(postContext, topicId)
}
在post.go中新增添加帖子的功能,并且更新map索引,使用RWLock保证并发安全,我在生成id时仅使用了简单的时间戳,但在不能保证在并发环境下的安全性。
func (*PostDao) AddPost(postContent string, topicId int64) error {
//check if topicId is valid
_, ok := topicIndexMap[topicId]
if !ok {
return errors.New("no topic id found")
}
//build the post struct
var post Post
post.Content = postContent
createTime := time.Now().Unix()
post.Id = createTime
post.ParentId = topicId
post.CreateTime = createTime
postLock.Lock()
posts := postIndexMap[topicId]
posts = append(posts, &post)
postIndexMap[topicId] = posts
postLock.Unlock()
//write into file
open, err := os.OpenFile("D:\\GO_PROGRAM\\go-project-example-0\\data\\post", os.O_WRONLY|os.O_APPEND, 0666)
if err != nil {
return err
}
defer open.Close()
write := bufio.NewWriter(open)
postBytes, err := json.Marshal(post)
if err != nil {
return err
}
_, err = write.WriteString("\n"+string(postBytes))
if err != nil {
return err
}
err = write.Flush()
if err != nil {
return err
}
return nil
}