第二节课记录|青训营笔记

140 阅读4分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第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()方法来实现加锁和解锁。

项目实战

本项目主要实现一个社会话题页面的后端,要求如下:

  1. 话题页面包括标题,文字描述和回帖列表
  2. 数据使用文件存储

课后作业要求添加以下内容:

  1. 支持发布帖子
  2. 帖子的id有唯一性
  3. 新增内容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
}