当前位置: 首页> 文旅> 文化 > 人力资源加盟代理哪个好_企业营销型网站分析_汨罗网站seo_查网站流量查询工具

人力资源加盟代理哪个好_企业营销型网站分析_汨罗网站seo_查网站流量查询工具

时间:2025/7/8 20:37:10来源:https://blog.csdn.net/2302_80713883/article/details/146704974 浏览次数:0次
人力资源加盟代理哪个好_企业营销型网站分析_汨罗网站seo_查网站流量查询工具

 🎁个人主页星云爱编程

 🔍所属专栏:【Go】 

🎉欢迎大家点赞👍评论📝收藏⭐文章

 长风破浪会有时,直挂云帆济沧海

目录

1.单元测试

1.1基本介绍

1.2单元测试编写步骤

1.3总结

1.4单元测试综合案例

2.goroutine

2.1进程和线程说明

2.2进程线程关系示意图

2.3并发和并行

 2.4Go协程和Go主线程

 2.5 goroutine使用案例

2.6MPG基本介绍

2.7设置Go运行cpu的个数

3.channel(管道)

3.1全局变量+互斥锁解决资源竞争

3.2channel的介绍

3.3channel的使用案例

3.4chan的关闭和遍历

3.4.1chan的关闭

3.4.3chan的遍历

3.5goroutine和channel结合

3.6管道阻塞的机制

3.7channel使用细节

结语


1.单元测试

1.1基本介绍

Go语言中自带有一个轻量级的测试框架testing和自带的go test命令来实现单元测试和性能测试,testing框架和其他语言中的测试框架类似,可以基于这个框架写针对相应函数的测试用例,也可以基于该框架写相应的压力测试用例。

通过单元测试,可以解决如下问题:

  1. 确保每个函数是可运行,并且运行结果是正确的
  2. 确保写出来的代码性能是好的
  3. 单元测试能及时的发现程序设计或实现的逻辑错误,使问题及早暴露,便于问题的定位解决,而性能测试的重点在于发现程序设计上的一些问题,让程序能够在高并发的情况下还能保持稳定

1.2单元测试编写步骤

(1)创建测试文件

测试文件以*test.go结尾,例如:math_test.go

(2)编写测试函数

测试函数以Test开头,接受*testing.T参数

func TestAdd(t *testing.T){//测试逻辑
}

(3)表格驱动测试

使用结构体切片定义多个测试用例,循环遍历执行

func TestAdd(t *testing.T){//使用结构体切片定义多个测试用例tests:=[]struct{a,b,want int}{{1,2,3},{3,4,7},{5,6,11},}//循环遍历执行for _,v:=range tests{sum:=Add(v.a,v.b)if sum != v.want{t.Errorf("Add(%d,%d)=%d;want %d\n",v.a,v.b,sum,v.want)}}
}

 (4)子测试

使用t.Run()分组测试用例,提升可读性和选择性运行

func TestAdd(t *testing.T){//使用结构体切片定义多个测试用例tests:=[]struct{name stringa,b,want int}{//分组{"正数",1,2,3},{"负数",-3,-4,-7},{"零值",0,0,0},}//循环遍历执行for _,v:=range tests{sum:=Add(v.a,v.b)if sum != v.want{t.Errorf("Add(%d,%d)=%d;want %d\n",v.a,v.b,sum,v.want)}}
}

(5)错误测试

验证函数是否返回预期错误

func TestDivide(t *testing.T){_,err:=Divide(6,0)if err ==nil{t.Fatal("预期错误,并未返回")}if err.Error()!="除零错误"{t.Error("错误消息不符:%s",err)}
}

(6)测试覆盖率

生成并查看覆盖率报告

go test -coverprofile=coverage.out
go tool cover -html=coverage.out

(7)初始化和清理

使用TestMain进行全局设置

func TestMain(m *testing.M){setup()code:=m.Run()teardown()os.Exit(code)
}

(8)使用t.Cleanup

注册清理函数

func TestDB(t *testing.T){db:=setupDB()t.Cleanup(func(){teardownDB(db)})//测试逻辑
}

(9)模拟依赖

通过接口实现Mock()

type Storage interface{Get(id int) string
}type MockStorage struct{}
func (m*MockStorage)Get(id int)string{return "mock"
}func TestService(t*testing.T){s:=&Service{storage:&MockStorage{}}//测试逻辑
}

(10)跳过测试

func TestNetwork(t *testing.T){if testing.Short(){t.Skip("短模式下跳过")}//网络相关测试
}

(11)并行测试

使用t.Parallel()加速测试

func TestParallel(t *testing.T){t.Parallel()//并发安全测试逻辑
}

1.3总结

  1. 测试用例文件名必须以 test.go结尾。比如 cal tert.go,cal 不是固定的。
  2. 测试用例函数必须以Test开头,一般来说就是Test+被测试的函数名,比如TestAddUpper。TestAddUpper(t *tesing.T)的形参类型必须是 *testing.T【看一下手册】
  3. 一个测试用例文件中,可以有多个测试用例函数,比如 TestAddUpper
  4. Testsub运行测试用例指令                                                                                        (1)cmd>go test[如果运行正确,无日志,错误时,会输出日志]                            (2)cmd>gotest-v[运行正确或是错误,都输出日志]
  5. 当出现错误时,可以使用t.Fatalf来格式化输出错误信息,并退出程序
  6. t.Logf方法可以输出相应的日志
  7. 测试用例函数,并没有放在main函数中,也执行了,这就是测试用例的方便之处
  8. PASS表示测试用例运行成功,FAIL表示测试用例运行失败

1.4单元测试综合案例

被测代码文件:mathutil.go

package main// 计算两个数的和
func Add(a, b int) int {return a + b
}// 计算阶乘
func Factorial(n int) int {if n < 0 {return -1}if n == 0 {return 1}return n * Factorial(n-1)
}// 判断是否为素数
func IsPrime(n int) bool {if n < 2 {return false}for i := 2; i*i <= n; i++ {if n%i == 0 {return false}}return true
}

测试文件:mathutil_test.go

package mainimport ("testing"
)func TestAdd(t *testing.T) {tests := []struct {a, b, expected int}{{1, 2, 3},{-1, 1, 0},{0, 0, 0},{100, 200, 300},}for _, tt := range tests {result := Add(tt.a, tt.b)if result != tt.expected {t.Errorf("Add(%d, %d) = %d; expected %d", tt.a, tt.b, result, tt.expected)}}
}func TestFactorial(t *testing.T) {tests := []struct {n, expected int}{{0, 1},{1, 1},{5, 120},{10, 3628800},{-1, -1},}for _, tt := range tests {result := Factorial(tt.n)if result != tt.expected {t.Errorf("Factorial(%d) = %d; expected %d", tt.n, result, tt.expected)}}
}func TestIsPrime(t *testing.T) {tests := []struct {n        intexpected bool}{{2, true},{3, true},{4, false},{17, true},{1, false},{0, false},{-1, false},}for _, tt := range tests {result := IsPrime(tt.n)if result != tt.expected {t.Errorf("IsPrime(%d) = %v; expected %v", tt.n, result, tt.expected)}}
}

运行:在bash下运行

go test -v

2.goroutine

2.1进程和线程说明

  • 进程就是程序程序在操作系统中的一次执行过程,是系统进行资源分配和调度的基本单位
  • 线程是进程的一个执行实例,是程序执行的最小单元,它是比进程更小的能独立运行的基本单位。
  • 一个进程可以创建核销毁多个线程,同一个进程中的多个线程可以并发执行。
  • 一个程序至少有一个进程,一个进程至少有一个线程

2.2进程线程关系示意图

2.3并发和并行

  1. 多线程程序在单核上运行,就是并发
  2. 多线程程序在多核上运行,就是并行
  • 并发:因为是在一个cpu上,比如有10个线程,每个线程执行10毫秒(进行轮询操作),从人的角度看,好像这10个线程都在运行,但是从微观上看,在某一个时间点看,其实只有一个线程在执行,这就是并发。
  • 并行:因为是在多个cpu上(比如有10个cpu),比如有10个线程,每个线程执行10毫秒(各自在不同cpu上执行),从人的角度看,这10个线程都在运行,但是从微观上看,在某一个时间点看,也同时有10个线程在执行,这就是并行

 

 

 2.4Go协程和Go主线程

Go主线程(有程序员直接称为线程/也可以理解成进程):一个Go线程上,可以起多个协程,你可以这样理解,协程是轻量级的线程[编译器做优化]。

Go协程的特点

  • 有独立的栈空间
  • 共享程序
  • 堆空间调度由用户控制
  • 协程是轻量级的线程

 2.5 goroutine使用案例

使用 go 关键字启动协程

package main
import("fmt""time"
)func test(){for i:=0;i<10;i++{fmt.Printf("test()~%v\n",i)time.Sleep(1000*time.Millisecond)//休眠一秒}
}func main(){//启动协程go test()for i:=0;i<5;i++{fmt.Printf("main()~%v\n",i)time.Sleep(1000*time.Millisecond)//休眠一秒}
}

说明:

  • 主线程是一个物理线程,直接作用在cpu上的。是重量级的,非常耗费cpu资源
  • 协程从主线程开启的,是轻量级的线程,是逻辑态。对资源消耗相对小。
  • Golang的协程机制是重要的特点,可以轻松的开启上万个协程。其它编程语言的并发机制是一般基于线程的,开启过多的线程,资源耗费大,这里就突显Golang在并发上的优势了
  • 当主线程终止时,其他的协程也将终止

2.6MPG基本介绍

  1. M 代表着一个内核线程,也可以称为一个工作线程。goroutine就是跑在M之上的
  2. P 代表着(Processor)处理器 它的主要用途就是用来执行goroutine的,一个P代表执行一个Go代码片段的基础(可以理解为上下文环境),所以它也维护了一个可运行的goroutine队列,和自由的goroutine队列,里面存储了所有需要它来执行的goroutine。
  3. G 代表着goroutine 实际的数据结构(就是你封装的那个方法),并维护者goroutine 需要的栈、程序计数器以及它所在的M等信息。
  4. Seched 代表着一个调度器 它维护有存储空闲的M队列和空闲的P队列,可运行的G队列,自由的G队列以及调度器的一些状态信息等。

2.7设置Go运行cpu的个数

所用到的方法:

 案例:

package main
import ("fmt""runtime"
)func main(){//获取当前(逻辑)cpu的数量num:=runtime.NumCPU()//设置num-1的cpu运行go程序runtime.GOMAXPROCS(num)fmt.Println(num)
}

3.channel(管道)

3.1全局变量+互斥锁解决资源竞争

案例: 启动20个协程求1~20的阶乘

package main
import ("fmt""sync""time"
)var (myMap =make(map[int]int,10)//声明一个全局的互斥锁lock sync.Mutex
)func test(n int){res:=1for i:=1;i<=n;i++{res*=i}//这里我们将res放入myMap中//加锁lock.Lock()myMap[n]=res//解锁lock.Unlock()
}func main(){//开启多个协程完成求阶乘for i:=1;i<=20;i++{go test(i)}//休眠5秒time.Sleep(5*time.Second)//加锁lock.Lock()for i,v:=range myMap{fmt.Printf("map[%d]=%d\n",i,v)}//解锁lock.Unlock()}

3.2channel的介绍

  1. channel本质就是一个数据结构-队列
  2. 数据是先进先出[FIFO]
  3. 线程安全,多goroutine访问时,不需要加锁,就是说channel本身就是线程安全的
  4. channel是类型安全的,只能发送和接收指定类型的数据,例如:一个string的channel只能存放string类型数据
  5. 无缓冲channel是同步的,发送和接收操作会阻塞直到另一端准备好
  6. 缓冲channel允许在没有接收者的情况下发送有限数量的数据
  7. channel可以通过 close() 函数关闭,关闭后不能再发送数据

 (1)定义channel

var 名字 chan 类型 

(2)创建channel

ch := make(chan int) // 创建一个无缓冲的int类型channel
ch := make(chan string, 10) // 创建一个缓冲大小为10的string类型channel

(3)发送和接受数据

ch <- 42 // 发送数据到channel
value := <-ch // 从channel接收数据
<-ch    //也可以不接受数据,将数据推出

3.3channel的使用案例

(1)基本数据类型Chan

package main
import ("fmt"
)func main(){//定义一个接受int类型的channelvar intChan chan int//使用channel前需要makeintChan=make(chan int,4)//进channelintChan<-1intChan<-2intChan<-3intChan<-4// intChan<-5  注意,当我们给channel写入数据时,不能超过其容量//看看intChan是什么fmt.Printf("intChan 的值是%v,地址为%p,长度为%v,容量为%v\n",intChan,&intChan,len(intChan),cap(intChan))//从channel中取数据var num intnum=<-intChanfmt.Println("num=",num)fmt.Printf("intChan 的长度为%v,容量为%v\n",len(intChan),cap(intChan))//注意:在没有使用协程的情况下,如果我们的管道数据已经全部取出,继续取数据就会报deadlocknum2:=<-intChan<-intChan  //数据出channel 也可以不接受,相当于丢弃num3:=<-intChanfmt.Printf("num2=%d , num3=%d\n",num2,num3)//num2=2 , num3=4//数据出channel时先进先出的
}

(2)struct管道和map管道:

package main
import ("fmt"
)type Cat struct{Name stringAge int
}func main(){//structChancat1:=Cat{"jav",12}cat2:=Cat{"mimi",3}var catChan chan CatcatChan=make(chan Cat,3)catChan<-cat1catChan<-cat2fmt.Println(<-catChan)cat3:=<-catChanfmt.Println(cat3)//mapChanvar mapChan chan map[string]stringmapChan=make(chan map[string]string,5)m1:=make(map[string]string,10)m1["功法1"]="九幽玄天"m1["功法2"]="天罡决"mapChan<-m1m2:=make(map[string]string)m2["神通1"]="古神决"m2["神通2"]="呼风唤雨"mapChan<-m2//输出fmt.Println(<-mapChan)fmt.Println(<-mapChan)
}

(3)interface{}类型的chan

package main
import("fmt"
)type Person struct{Name stringAge int
}
type Dog struct{Name stringAge int
}func main(){//能接受任意类型的管道var allChan chan interface{}allChan=make(chan interface{},10)//不指定空间10则无法使用//定义变量num1:=2str1:="channel is interesting"person1:=Person{"李星云",21}dog1:=Dog{"小红",4}//chan接受数据allChan<-num1allChan<-str1allChan<-person1allChan<-dog1//取数据num2:=<-allChanstr2:=<-allChanperson2:=<-allChandog2:=<-allChan//打印fmt.Printf("num2的类型为%T,值为%v\n",num2,num2)fmt.Printf("str2的类型为%T,值为%v\n",str2,str2)fmt.Printf("person2的类型为%T,值为%v\n",person2,person2)fmt.Printf("dog2的类型为%T,值为%v\n",dog2,dog2)//验证// fmt.Println(person2.Name)//会报错,此时是interface{}类型,不能由属性//需类型断言解决该问题person3,ok:=person2.(Person)if ok==false{fmt.Println("类型断言失败")}fmt.Printf("person3的类型为%T,值为%v\n",person3,person3)fmt.Println(person3.Name) 
}

3.4chan的关闭和遍历

3.4.1chan的关闭

用到的函数:

使用内置函数close可以关闭channel,当channel关闭后,就不能再向channel写数据了,但是仍然可以从该channel读取数据

案例:

package main
import ("fmt"
)func main(){var intChan chan intintChan=make(chan int,5)intChan<-1intChan<-2intChan<-3//关闭管道close(intChan)//这时不能再写数据到intChan//intChan<-4//panic: send on closed channel//但是可以读数据fmt.Println(<-intChan)
}

3.4.3chan的遍历

channel支持for--range的方式进行遍历,

请注意两个细节:

  1. 在遍历时,如果channel没有关闭,则回出现deadlock的错误
  2. 在遍历时,如果channel已经关闭,则会正常遍历数据,遍历完后,就会退出遍历。

案例1:没有close

package main
import ("fmt"
)func main(){var intChan chan int =make(chan int,100)//遍历chan,接受数据for i:=0;i<100;i++{intChan<-i*2}//在遍历读数据时,如果channel没有关闭,//则会出现fatal error: all goroutines are asleep - deadlock!for v:=range intChan{fmt.Println(v)}fmt.Println("遍历完成~")//不会输出
}

 案例2:close

package main
import ("fmt"
)func main(){var intChan chan int =make(chan int,100)//遍历chan,接受数据for i:=0;i<100;i++{intChan<-i*2}//在遍历读数据时,如果channel已经关闭,//则会正常遍历数据,遍历完数据后就会退出遍历close(intChan)for v:=range intChan{fmt.Println(v)}fmt.Println("遍历完成~")//正常输出
}

3.5goroutine和channel结合

package main
import ("fmt"_ "time"
)//writer Data
func writeData(intChan chan int){for i:=1;i<=200;i++{//放入数据intChan<-i fmt.Println("writeData.............写入数据:",i)}//关闭close(intChan)
}//read Data
func readData(intChan chan int,exitChan chan bool){for{v,ok:=<-intChanif !ok{break}fmt.Printf("readData 读到数据=%v\n",v)}//读完数据exitChan<-trueclose(exitChan)
}func main(){//创建两个管道intChan := make(chan int,200)exitChan := make(chan bool,1)go writeData(intChan)go readData(intChan,exitChan)// time.Sleep(5*time.Second)for {_,ok:=<-exitChanif !ok{break}}
}

3.6管道阻塞的机制

(1)无缓冲管道 :

  • 发送操作会阻塞,直到有另一个goroutine执行接收操作
  • 接收操作会阻塞,直到有另一个goroutine执行发送操作
  • 这种机制实现了goroutine之间的同步

(2)有缓冲管道 :

  • 当缓冲区未满时,发送操作不会阻塞
  • 当缓冲区为空时,接收操作会阻塞
  • 当缓冲区满时,发送操作会阻塞
  • 这种机制允许一定程度的异步操作

案例:

package mainimport ("fmt""time"
)func main() {ch := make(chan int) // 无缓冲管道go func() {time.Sleep(2 * time.Second)fmt.Println("准备发送数据")ch <- 42fmt.Println("数据已发送")}()fmt.Println("等待接收数据")value := <-chfmt.Println("接收到数据:", value)
}

解除阻塞的方式 :

  • 对于发送操作:有goroutine执行接收操作
  • 对于接收操作:有goroutine执行发送操作
  • 使用context或timeout机制取消阻塞
  • 关闭管道(关闭后接收操作不会阻塞,会立即返回零值)

解除阻塞案例:

package mainimport ("fmt""time"
)func main() {ch := make(chan int)// 启动一个goroutine,延迟发送数据go func() {time.Sleep(2 * time.Second)ch <- 42fmt.Println("数据已发送")}()// 主goroutine尝试接收数据,会阻塞直到有数据fmt.Println("等待接收数据...")value := <-chfmt.Println("接收到数据:", value)// 使用select实现超时解除阻塞select {case v := <-ch:fmt.Println("接收到数据:", v)case <-time.After(1 * time.Second):fmt.Println("超时,解除阻塞")}// 关闭管道解除阻塞close(ch)v, ok := <-chfmt.Println("从关闭的管道接收:", v, "是否成功:", ok)
}

3.7channel使用细节

  • 避免向已关闭的channel发送数据,这会导致panic
  • 重复关闭channel会导致panic
  • 从已关闭的channel接收数据会立即返回零值
  • 使用nil channel会永久阻塞
  • 注意channel的缓冲区大小,过大会占用过多内存
  • 使用 select 语句可以同时处理多个channel操作
  • 对于只读或只写channel,可以使用类型约束( <-chan 和 chan<- )
  • 使用 range 可以遍历channel直到它被关闭

结语

感谢您的耐心阅读,希望这篇博客能够为您带来新的视角和启发。如果您觉得内容有价值,不妨动动手指,给个赞👍,让更多的朋友看到。同时,点击关注🔔,不错过我们的每一次精彩分享。若想随时回顾这些知识点,别忘了收藏⭐,让知识触手可及。您的支持是我们前进的动力,期待与您在下一次分享中相遇!

关键字:人力资源加盟代理哪个好_企业营销型网站分析_汨罗网站seo_查网站流量查询工具

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com

责任编辑: