golang1.协程2.测试3.依赖机制4.青训营界面后端简单实现| 青训营

241 阅读5分钟

goroutine

go语言协程简单实现

`package concurrence
import (
	"fmt"
	"time"
)

func hello(i int) {
	println("hello goroutine : " + fmt.Sprint(i))
}

func HelloGoRoutine() {
	for i := 0; i < 5; i++ {
		go func(j int) {
			hello(j)
		}(i)
	}
	time.Sleep(time.Second)
}`

各个协程之间通过channel进行通讯

`package concurrence

func CalSquare() {
	src := make(chan int)
	dest := make(chan int, 3)
	go func() {
		defer close(src)
		for i := 0; i < 10; i++ {
			src <- i
		}
	}()
	go func() {
		defer close(dest)
		for i := range src {
			dest <- i * i
		}
	}()
	for i := range dest {
		//复杂操作
		println(i)
	}
}`

并发机制导致多个协程争夺内存空间,通过sync.lock实现锁机制,进而实现独立实现不争夺空间

`package concurrence

import (
	"sync"
	"time"
)

var (
	x    int64
	lock sync.Mutex
)

func addWithLock() {
	for i := 0; i < 2000; i++ {
		lock.Lock()
		x += 1
		lock.Unlock()
	}
}
func addWithoutLock() {
	for i := 0; i < 2000; i++ {
		x += 1
	}
}

func Add() {
	x = 0
	for i := 0; i < 5; i++ {
		go addWithoutLock()
	}
	time.Sleep(time.Second)
	println("WithoutLock:", x)
	x = 0
	for i := 0; i < 5; i++ {
		go addWithLock()
	}
	time.Sleep(time.Second)
	println("WithLock:", x)
}
`

通过sync.waitgroup实现和sync.lock相同的功能

例子一:

`package main
import( "sync"
      "fmt"
      )

//Add(delta int)表示向内部计数器添加增量(delta),其中参数delta可以是负数
//Done()表示减少WaitGroup计数器的值,应当在程序最后执行.相当于Add(-1)
//Wait()表示阻塞直到WaitGroup计数器为0
func hello(){
  fmt.Println("Hello world")
}
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()
}`

例子二:

`package main

import (
   "fmt"
   "sync"
)

var wg sync.WaitGroup

func main() {

   for i := 1; i <= 3; i++ {
      wg.Add(1)
      go demo(i)
   }
   //阻塞,知道WaitGroup队列中所有任务执行结束时自动解除阻塞
   fmt.Println("开始阻塞")
   wg.Wait()
   fmt.Println("任务执行结束,解除阻塞")

}

func demo(index int) {
   for i := 1; i <= 5; i++ {
      fmt.Printf("第%d次执行,i的值为:%d\n", index, i)
   }
   wg.Done()
}`

依赖机制

GOPATH:

`GOPATH是一个环境,就是一个目录
默认在~/go(unix,linux),%USERPROFILE%\go(windows)
管理方式:给一个目录,所有的依赖都到GOPATH下去找
GOPATH要求在目录下必须有一个src目录,文件都放在$GOPATH/src目录下
问题:所有的项目都在GOPATH目录下面,依赖库也放在GOPATH下面,导致GOPATH越来越大`

GOPATH 的缺点

`go get 命令的时候,无法指定获取的版本
引用第三方项目的时候,无法处理v1、v2、v3等不同版本的引用问题,因为在GOPATH 模式下项目路径都是 github.com/foo/project
无法同步一致第三方版本号,在运行 Go 应用程序的时候,无法保证其它人与所期望依赖的第三方库是相同的版本。`

GoVender

`govender :每个项目都有自己的vendor目录,存放第三方库
GoVender的缺点
依赖包全部都在vendor目录下,每个项目都有一份,所以每次拉取项目时都会拉一遍依赖。
govendor不区分包版本,意味着开发期间拉的依赖的包很可能跟上线后的拉的依赖包版本不一致,很危险。
govendor add +e会拉取全部外部包,即使是本项目没有用到的,这样会造成大量的冗余。 但是只用govendor add +指定包又很麻烦。`

GoMod:

go mod的优点:

`项目路径可以脱离 $GOPATH 了,不需要必须放在 $GOPATH/src 下。
项目内部模块的引入是基于 moduleName 而不再死板的基于 projectName 了。
半自动维护依赖,如果你很懒,你甚至可以不需要使用 get 预先安装依赖, module 在 run test build 时会检测未下载的依赖,并自动下载它们。`

测试

单元测试

judgement.go:

`package test

func JudgePassLine(score int16) bool {
	if score >= 60 {
		return true
	}
	return false
}`

judgement_test.go:

`package test

import (
	"github.com/stretchr/testify/assert"
	"testing"
)
//测试率达到66.7%
func TestJudgePassLineTrue(t *testing.T) {
	isPass := JudgePassLine(70)
  //对比结果是否相同
	assert.Equal(t, true, isPass)
}
//测试率达到100%
func TestJudgePassLineFail(t *testing.T) {
	isPass := JudgePassLine(50)
	assert.Equal(t, false, isPass)
}`

含有文档的单元测试

test.go

(依赖于文档)


`package test

import (
	"bufio"
	"os"
	"strings"
)

func ReadFirstLine() string {
	open, err := os.Open("log")
	defer open.Close()
	if err != nil {
		return ""
	}
	scanner := bufio.NewScanner(open)
	for scanner.Scan() {
		return scanner.Text()
	}
	return ""
}

func ProcessFirstLine() string {
	line := ReadFirstLine()
	destLine := strings.ReplaceAll(line, "11", "00")
	return destLine
}`

test_test.go

使用monkey来打桩(不依赖于文档)


`package test

import (
	"bou.ke/monkey"
	"github.com/stretchr/testify/assert"
	"testing"
)
//这种测试依赖于原来的文件夹的文档
func TestProcessFirstLine(t *testing.T) {
	firstLine := ProcessFirstLine()
	assert.Equal(t, "line00", firstLine)
}
//使用monkey进行打桩就不依赖于原来的文件
func TestProcessFirstLineWithMock(t *testing.T) {
	monkey.Patch(ReadFirstLine, func() string {
		return "line110"
	})
	defer monkey.Unpatch(ReadFirstLine)
	line := ProcessFirstLine()
	assert.Equal(t, "line000", line)
}
`

基准测试

book.go

`package benchmark

import (
	"github.com/bytedance/gopkg/lang/fastrand"
	"math/rand"
)

var ServerIndex [10]int

func InitServerIndex() {
	for i := 0; i < 10; i++ {
		ServerIndex[i] = i+100
	}
}

func Select() int {
	return ServerIndex[rand.Intn(10)]
}
//fastrand.Intn(10) 是来自 github.com/dgryski/fastrand 包的函数,它是一个快速的伪随机数生成器。它的实现使用了一种特殊的算法,以提供更高的性能。
//由于 fastrand.Intn(10) 使用了一种特殊的算法,它可能在性能上比标准库中的 rand.Intn(10) 更高效。
func FastSelect() int {
	return ServerIndex[fastrand.Intn(10)]
}

book_test.go

package benchmark

import (
	"testing"
)

func BenchmarkSelect(b *testing.B) {
	InitServerIndex()//InitServerIndex() 是一个用于初始化服务器索引的函数。
	b.ResetTimer()//b.ResetTimer() 是一个用于重置计时器的函数。去除初始化的时间
	// 并发和并行的问题,这个基准测试函数的并发与并行取决于 Select() 函数的实现。
        //如果 Select() 函数是并发执行的,那么在每次迭代中可能会有多个 Select() 函数同时运行。
        //如果 Select() 函数是并行执行的,那么在每次迭代中可能会有多个 Select() 函数同时运行,
        //并且它们之间可能会并行执行。
  for i := 0; i < b.N; i++ {
		Select() //我们执行 Select() 函数 b.N 次,其中 b.N 表示基准测试的迭代次数。
	}
}
func BenchmarkSelectParallel(b *testing.B) {
	InitServerIndex()
	b.ResetTimer()
  //b.RunParallel 方法用于并行执行基准测试函数。它接受一个函数作为参数,
  //该函数在并行执行的每个迭代中被调用。
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			Select()
		}
	})
}
func BenchmarkFastSelectParallel(b *testing.B) {
	InitServerIndex()
	b.ResetTimer()
	b.RunParallel(func(pb *testing.PB) {
		for pb.Next() {
			FastSelect()
		}
	})
}`

青训营后端界面功能实现:

数据层:

topic.go


`package repository

import (
	"sync"
)

type Topic struct {
	Id         int64  `json:"id"`
	Title      string `json:"title"`
	Content    string `json:"content"`
	CreateTime int64  `json:"create_time"`
}
type TopicDao struct {
}
var (
	topicDao  *TopicDao
	topicOnce sync.Once
)
func NewTopicDaoInstance() *TopicDao {
	topicOnce.Do(
		func() {
			topicDao = &TopicDao{}
		})
	return topicDao
}
func (*TopicDao) QueryTopicById(id int64) *Topic {
	return topicIndexMap[id]
}`

post.go:


`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
  //sync.Once 是Go语言中的一个并发原语,用于实现只执行一次的操作。
  
  // 在Go语言中,sync.Once 类型提供了一个 Do 方法,用于执行一个函数,
  //但只会执行一次,无论 Do 方法被调用多少次。
  
  // 具体来说,sync.Once 保证在多个Goroutine并发调用 Do 方法时,
  //只有第一个调用会执行其中的函数,而其他调用会被阻塞直到第一个调用完成。
  
  // sync.Once 的常见用途是在并发环境下进行初始化操作,
  //以确保只有一个Goroutine执行初始化逻辑。
    //   var once sync.Once
    // var initialized bool
    // func initialize() {
    //     // 初始化操作
    //     initialized = true
    // }

    // func main() {
    //     // 并发调用 initialize 函数,但只会执行一次
    //     for i := 0; i < 10; i++ {
    //         go func() {
    //             once.Do(initialize)
    //             fmt.Println("Initialized:", initialized)
    //         }()
    //     }

    //     // 等待一段时间,以确保所有Goroutine执行完成
    //     time.Sleep(1 * time.Second)
    // }
            postOnce sync.Once
    )
func NewPostDaoInstance() *PostDao {
	postOnce.Do(
		func() {
			postDao = &PostDao{}
		})
	return postDao
}
func (*PostDao) QueryPostsByParentId(parentId int64) []*Post {
	return postIndexMap[parentId]
}

db_init.go


package repository

import (
	"bufio"
	"encoding/json"
	"os"
)

var (
	topicIndexMap map[int64]*Topic
	postIndexMap  map[int64][]*Post
)

func Init(filePath string) error{
	if err := initTopicIndexMap(filePath);err!=nil{
		return err
	}
	if err := initPostIndexMap(filePath);err!=nil{
		return err
	}
	return nil
}

func initTopicIndexMap(filePath string) error {
	open, err := os.Open(filePath + "topic")
	if err != nil {
		return err
	}
	scanner := bufio.NewScanner(open)
	topicTmpMap := make(map[int64]*Topic)
	for scanner.Scan() {
		text := scanner.Text()
		var topic Topic
		if err := json.Unmarshal([]byte(text), &topic); err != nil {
			return err
		}
		topicTmpMap[topic.Id] = &topic
	}
	topicIndexMap = topicTmpMap
	return nil
}

func initPostIndexMap(filePath string) error{
	open, err := os.Open(filePath + "post")
	if err != nil {
		return err
	}
	scanner := bufio.NewScanner(open)
	postTmpMap := make(map[int64][]*Post)
	for scanner.Scan() {
		text := scanner.Text()
		var post Post
		if err := json.Unmarshal([]byte(text), &post); err != nil {
			return err
		}
		posts, ok := postTmpMap[post.ParentId]
		if !ok {
			postTmpMap[post.ParentId] = []*Post{&post}
			continue
		}
		posts = append(posts, &post)
		postTmpMap[post.ParentId] = posts
	}
	postIndexMap = postTmpMap
	return nil
}`

data:

post:

`{"id":1,"parent_id":1,"content":"小姐姐快来1","create_time":1650437616}
{"id":2,"parent_id":1,"content":"小姐姐快来2","create_time":1650437617}
{"id":3,"parent_id":1,"content":"小姐姐快来3","create_time":1650437618}
{"id":4,"parent_id":1,"content":"小姐姐快来4","create_time":1650437619}
{"id":5,"parent_id":1,"content":"小姐姐快来5","create_time":1650437620}
{"id":6,"parent_id":2,"content":"小哥哥快来1","create_time":1650437621}
{"id":7,"parent_id":2,"content":"小哥哥快来2","create_time":1650437622}
{"id":8,"parent_id":2,"content":"小哥哥快来3","create_time":1650437623}
{"id":9,"parent_id":2,"content":"小哥哥快来4","create_time":1650437624}
{"id":10,"parent_id":2,"content":"小哥哥快来5","create_time":1650437625}`

topic:

`{"id":1,"title":"青训营来啦!","content":"小姐姐,快到碗里来~","create_time":1650437625}
{"id":2,"title":"青训营来啦!","content":"小哥哥,快到碗里来~","create_time":1650437640}`

逻辑层:

`package service

import (
	"errors"
	"github.com/Moonlight-Zhao/go-project-example/repository"
	"sync"
)

type PageInfo struct {
	Topic    *repository.Topic
	PostList []*repository.Post
}

func QueryPageInfo(topicId int64) (*PageInfo, error) {
	return NewQueryPageInfoFlow(topicId).Do()
}

func NewQueryPageInfoFlow(topId int64) *QueryPageInfoFlow {
	return &QueryPageInfoFlow{
		topicId: topId,
	}
}

type QueryPageInfoFlow struct {
	topicId  int64
	pageInfo *PageInfo

	topic   *repository.Topic
	posts   []*repository.Post
}

func (f *QueryPageInfoFlow) Do() (*PageInfo, error) {
	if err := f.checkParam(); err != nil {
		return nil, err
	}
	if err := f.prepareInfo(); err != nil {
		return nil, err
	}
	if err := f.packPageInfo(); err != nil {
		return nil, err
	}
	return f.pageInfo, nil
}

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
}`

控制层


`package cotroller

import (
	"strconv"

	"github.com/Moonlight-Zhao/go-project-example/service"
)

type PageData struct {
	Code int64       `json:"code"`
	Msg  string      `json:"msg"`
	Data interface{} `json:"data"`
}
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,
	}

}
`