Go语言入门-工程实践 | 青训营笔记

84 阅读3分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的的第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

测试

两个概念:

幂等: 多次重复运行同一个单元测试,结果是一样的

稳定: 单元测试相互隔离的,任何时间单元测试的任何函数能够独立的运行。依赖外部环境可能受网络等影响不能在任意时间都能独立运行。

项目实战

分层结构

组件工具

课后实践

  1. 支持发布帖子
  2. 本地id生成需要保证不重复,唯一性
  3. 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)
}