Go 语言工程进阶 | 青训营笔记

44 阅读1分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第2天 可能我没什么基础吧,昨天的课没咋听懂,今天的课就听的一头雾水,只能记录点自己勉强能听懂的东西了,go语言学习的路程真是艰难啊

Go语言为什么这么快? 因为Go可以充分发挥多核优势,高效运行

协程Goroutine 协程:用户态,轻量级线程,栈KB级别。 线程:内核态,线程跑多个协程,栈MB级别。 这使得Go语言一次可以创建上万级别的协程。 通过go关键字来开启goroutine,是一个轻量级线程,其调度是由Golang运行时进行管理的 代码案例: package main

import ( "fmt" "time" )

func HelloPrint(i int) { // println("Hello goroutine : " + fmt.Sprint(i)) fmt.Println("Hello goroutine :", i) }

// 效果就是快速且无序打印

func HelloGoroutine() { for i := 0; i < 5; i++ { go func(j int) { HelloPrint(j) }(i) } // time.Sleep()的作用是:保证了子协程在执行完之前,主协程不退出。 time.Sleep(time.Second) }

func main() { HelloGoroutine() }

有通过通信共享内存和通过共享内存实现通信两种方法 提倡通过通信共享内存而不是通过共享内存而实现通信

通道Channel make(chan 元素类型,[缓冲大小]) 无缓冲通道 make(chan int) 有缓冲通道 make(chan int,2)

无缓冲通道也被称为同步通道; 有缓冲通道可以类比红石中继器(但是有点不一样,只是效果类似);

通道是用来传递数据的一个数据结构,可以用于两个goroutine之间,通过传递一个指定类型的值来同步运行和通讯。 操作符<-用于指定通道的方向,实现发送or接收; 特别地,若未指定方向,则为双向通道; 通道在使用前必须先创建;

ch <- v // 把 v 发送到通道 ch v := <-ch // 从 ch 接收数据 并把值赋给 v

package main

import ( "fmt" )

func CalcPow() { src := make(chan int) dest := make(chan int, 3) // 子协程src发送0~9数字 go func() { defer close(src) // 当子协程src结束的时候再关闭,减少资源浪费 for i := 0; i < 10; i++ { src <- i } }() // 子协程dest计算输入数字的平方 go func() { defer close(dest) // 通过 range 关键字来实现遍历读取到的数据 for i := range src { dest <- (i * i) } }() // 主协程输出最后的答案 // 这里可以暂时认为子协程需要使用匿名函数 for i := range dest { // 因为主协程可能会有更多的复杂操作,比较耗时,所以用带缓冲的通道可以避免问题 fmt.Println(i) } }

func main() { CalcPow() }

并发安全Lock 当采用共享内存实现通信(上图的第二种)的时候,会发生一些Undefine问题,这时候就需要并发安全。 Mutex互斥锁来实现同步 同步的含义:两个或多个协程之间互相等待,而不是顺次执行。

package main

import ( "fmt" "sync" "time" )

// 除了使用channel实现同步之外,还可以使用Mutex互斥锁来实现同步。

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) fmt.Println("WithoutLock :", x)

x = 0
for i := 0; i < 5; i++ {
	go AddWithLock()
}
time.Sleep(time.Second)
fmt.Println("WithLock :", x)

}

func main() { Add() } /* Output: WithoutLock : 8014 WithLock : 10000 */

单元测试: 回归测试,集成测试,单元测试 从左到右,覆盖率逐层变大,成本却逐层降低

单元测试规则
 所有测试文件以_test.go结尾
 func TestXxx(*testing.T)
 初始化逻辑放到TestMain中

代码覆盖率:
 衡量代码是否经过了足够的测试
 评价项目的测试水准
 评估项目是否达到了高水准测试等级

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

func TestJudgePassLineTrue(t *testing.T) {
	isPass := JudgeePassLine(70)
	assert.Equal(t, true, isPass)
}

func TestJudgePassLineFalse(t *testing.T) {
	isPass := JudgeePassLine(50)
	assert.Equal(t, false, isPass)
}
一般覆盖率在50%~60%,较高覆盖率在80%+
 测试分支相互独立、全面覆盖
 测试单元粒度足够小,函数单一职责

单元测试-依赖
 幂等:重复运行同一个case,结果与之前一致
 稳定:指单元测试相互隔离,可以独立运行

单元测试-文件处理
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") // 替换11为00
	return destLine
}

func TestProcessFirstLine(t *testing.T) {
	firstLine := ProcessFirstLine()
	assert.Equal(t, "line00", firstLine)
}