页面加载中,请稍候
来源:嵌入式应用研究院发布时间:2023-05-241540浏览
询问 AI点击上方“嵌入式应用研究院”,选择“置顶/星标公众号”
干货福利,第一时间送达!来源 | golang编程笔记
整理&排版| 嵌入式应用研究院
可变参数是空接口类型当参数的可变参数是空接口类型时,传入空接口的切片时需要注意参数展开的问题。
funcmain(){
vara=[]interface{}{1,2,3}
fmt.Println(a)//传入切片类型
fmt.Println(a...)//传入切片的值
}
不管是否展开,编译器都无法发现错误,但是输出是不同的:
[123]数组是值传递
123
在函数调用参数中,数组是值传递,无法通过修改数组类型的参数返回结果。必要时需要使用切片。
funcmain(){
x:=[3]int{1,2,3}
func(arr[3]int){
arr[0]=7
fmt.Println(arr)
}(x)
fmt.Println(x)
}
map是一种hash表实现,每次遍历的顺序都可能不一样。
funcmain(){返回值被屏蔽
m:=map[string]string{
"1":"1",
"2":"2",
"3":"3",
}
fork,v:=rangem{
println(k,v)
}
}
在局部作用域中,命名的返回值内同名的局部变量屏蔽:
funcFoo()(errerror){recover必须在defer函数中运行
iferr:=Bar();err!=nil{//内部变量err与返回值err冲突了
return
}
return
}
recover捕获的是祖父级调用时的异常,直接调用时无效:
funcmain(){
recover()
panic(1)
}
直接defer调用也是无效:
funcmain(){
deferrecover()
panic(1)
}
defer调用时多层嵌套依然无效:
funcmain(){
deferfunc(){
func(){recover()}()
}()
panic(1)
}
必须在defer函数中直接调用才有效:
funcmain(){main函数提前退出
deferfunc(){
recover()
}()
panic(1)
}
后台Goroutine无法保证完成任务。
funcmain(){独占CPU导致其它Goroutine饿死
goprintln("hello")//main不会等待新协程,新协程可能被中断
}
Goroutine是协作式抢占调度,Goroutine本身不会主动放弃CPU:
funcmain(){
runtime.GOMAXPROCS(1)
gofunc(){
fori:=0;i<10;i++{
fmt.Println(i)
}
}()
for{}//占用CPU
}
解决的方法是在for循环加入runtime.Gosched()调度函数:
funcmain(){
runtime.GOMAXPROCS(1)
gofunc(){
fori:=0;i<10;i++{
fmt.Println(i)
}
}()
for{
runtime.Gosched()
}
}
或者是通过阻塞的方式避免CPU占用:
funcmain(){不同Goroutine之间不满足顺序一致性内存模型
runtime.GOMAXPROCS(1)
gofunc(){
fori:=0;i<10;i++{
fmt.Println(i)
}
os.Exit(0)
}()
select{}
}
因为在不同的Goroutine,main函数中无法保证能打印出hello, world:
varmsgstring
vardonebool
funcsetup(){
msg="hello,world"
done=true//在main协程中看来,done语句不一定在msg之后完成
}
funcmain(){
gosetup()
for!done{
}
println(msg)
}
解决的办法是用chan显式同步:
varmsgstring
vardone=make(chanbool)
funcsetup(){
msg="hello,world"
done<-true
}
funcmain(){
gosetup()
<-done
println(msg)
}
msg的写入是在channel发送之前,所以能保证打印hello, world
defer错误引用同一个变量funcmain(){
fori:=0;i<5;i++{
deferfunc(){
println(i)//这里打印一直是5,不是4,3,2,1,0
}()
}
}
改进的方法是通过函数参数传入:
funcmain(){切片会导致整个底层数组被锁定
fori:=0;i<5;i++{
deferfunc(iint){
println(i)
}(i)
}
}
切片会导致整个底层数组被锁定,底层数组无法释放内存。如果底层数组较大会对内存产生很大的压力。
funcmain(){
headerMap:=make(map[string][]byte)
fori:=0;i<5;i++{
name:="/path/to/file"
data,err:=ioutil.ReadFile(name)
iferr!=nil{
log.Fatal(err)
}
//创建了data的切片,导致data不会被释放,文件全部内容一直在内存中
headerMap[name]=data[:1]
}
//dosomething
}
解决的方法是将结果克隆一份,这样可以释放底层的数组:
funcmain(){空指针和空接口不等价
headerMap:=make(map[string][]byte)
fori:=0;i<5;i++{
name:="/path/to/file"
data,err:=ioutil.ReadFile(name)
iferr!=nil{
log.Fatal(err)
}
headerMap[name]=append([]byte{},data[:1]...)
}
//dosomething
}
比如返回了一个错误指针,但是并不是空的error接口:
funcreturnsError()error{内存地址会变化
varp*MyError=nil
ifbad(){
p=ErrBad
}
returnp//Willalwaysreturnanon-nilerror.
}
Go语言中对象的地址可能发生变化,因此指针不能从其它非指针类型的值生成:
funcmain(){
varxint=42
varpuintptr=uintptr(unsafe.Pointer(&x))
runtime.GC()//运行垃圾回收,内存地址会发送变化
varpx*int=(*int)(unsafe.Pointer(p))
println(*px)
}
当内存发送变化的时候,相关的指针会同步更新,但是非指针类型的uintptr不会做同步更新。同理CGO中也不能保存Go对象地址。
Goroutine泄露Go语言是带内存自动回收的特性,因此内存一般不会泄漏。但是Goroutine确存在泄漏的情况,同时泄漏的Goroutine引用的内存同样无法被回收。
funcmain(){
ch:=func()<-chanint{
ch:=make(chanint)
gofunc(){
fori:=0;;i++{
ch<-i
}
}()
returnch
}()
forv:=rangech{
fmt.Println(v)
ifv==5{
break//break之后,主协程不再读取chan,新协程会一直阻塞
}
}
}
上面的程序中后台Goroutine向管道输入自然数序列,main函数中输出序列。但是当break跳出for循环的时候,后台Goroutine就处于无法被回收的状态了。我们可以通过context包来避免这个问题:
funcmain(){
ctx,cancel:=context.WithCancel(context.Background())
ch:=func(ctxcontext.Context)<-chanint{
ch:=make(chanint)
gofunc(){
fori:=0;;i++{
select{
case<-ctx.Done():
return
casech<-i:
}
}
}()
returnch
}(ctx)
forv:=rangech{
fmt.Println(v)
ifv==5{
cancel()
break
}
}
}
当main函数在break跳出循环时,通过调用cancel()来通知后台Goroutine退出,这样就避免了Goroutine的泄漏。
新闻来源:嵌入式应用研究院,文中所述为作者独立观点,不代表icspec立场。更多精彩资讯请下载icspec App。如对本稿件有异议,请联系微信客服specltkj。
暂无评论哦,快来评论一下吧!
2026-06-25

2026-07-10