首页 > 其他分享 >Go语言错误总结(五)

Go语言错误总结(五)

时间:2022-11-25 12:34:10浏览次数:25  
标签:总结 http 错误 err fmt Println func Go main


29、未导出的结构体不会被编码

以小写字母开头的结构体将不会被(json、xml、gob等)编码,因此当你编码这些未导出的结构体时,你将会得到零值。

package main
import (
"encoding/json"
"fmt"
)
type MyData struct {
One int
two string
}
func main() {
in := MyData{1, "two"}
fmt.Printf("%#v\n", in) //prints main.MyData{One:1, two:"two"}
encoded, _ := json.Marshal(in)
fmt.Println(string(encoded)) //prints {"One":1}
var out MyData
json.Unmarshal(encoded, &out)
fmt.Printf("%#v\n", out) //prints main.MyData{One:1, two:""}
}

运行结果:

main.MyData{One:1, two:"two"}
{"One":1}
main.MyData{One:1, two:""}

30、有活动的 Goroutines 下的应用退出

应用将不会等待所有的goroutines完成。这对于初学者而言是个很常见的错误。

package main
import (
"fmt"
"time"
)
func main() {
workerCount := 2
for i := 0; i < workerCount; i++ {
go doit(i)
}
time.Sleep(1 * time.Second)
fmt.Println("all done!")
}
func doit(workerId int) {
fmt.Printf("[%v] is running\n", workerId)
time.Sleep(3 * time.Second)
fmt.Printf("[%v] is done\n", workerId)
}

运行结果:

[0] is running
[1] is running
all done!

一个最常见的解决方法是使用“WaitGroup”变量。它将会让主goroutine等待所有的worker goroutine完成。如果你的应用有长时运行的消息处理循环的worker,你也将需要一个方法向这些goroutine发送信号,让它们退出。你可以给各个worker发送一个“kill”消息。另一个选项是关闭一个所有worker都接收的channel。这是一次向所有goroutine发送信号的简单方式。

package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
done := make(chan struct{})
workerCount := 2
for i := 0; i < workerCount; i++ {
wg.Add(1)
go doit(i, done, wg)
}
close(done)
wg.Wait()
fmt.Println("all done!")
}
func doit(workerId int, done <-chan struct{}, wg sync.WaitGroup) {
fmt.Printf("[%v] is running\n", workerId)
defer wg.Done()
<-done
fmt.Printf("[%v] is done\n", workerId)
}

如果你运行这个应用,你将会看到:


[1] is running
[1] is done
[0] is running
[0] is done
fatal error: all goroutines are asleep - deadlock!

看起来所有的worker在主goroutine退出前都完成了。为什么会出现死锁?worker退出了,它们也执行了wg.Done()。应用应该没问题啊。

死锁发生是因为各个worker都得到了原始的“WaitGroup”变量的一个拷贝。当worker执行wg.Done()时,并没有在主 goroutine上 的“WaitGroup”变量上生效。

package main
import (
"fmt"
"sync"
)
func main() {
var wg sync.WaitGroup
done := make(chan struct{})
wq := make(chan interface{})
workerCount := 2
for i := 0; i < workerCount; i++ {
wg.Add(1)
go doit(i, wq, done, &wg)
}
for i := 0; i < workerCount; i++ {
wq <- i
}
close(done)
wg.Wait()
fmt.Println("all done!")
}
func doit(workerId int, wq <-chan interface{}, done <-chan struct{}, wg *sync.WaitGroup) {
fmt.Printf("[%v] is running\n", workerId)
defer wg.Done()
for {
select {
case m := <-wq:
fmt.Printf("[%v] m => %v\n", workerId, m)
case <-done:
fmt.Printf("[%v] is done\n", workerId)
return
}
}
}

运行结果:

[0] is running
[0] m => 0
[1] is running
[1] is done
[0] m => 1
[0] is done
all done!

现在它会如预期般工作

31、向无缓存的Channel发送消息,只要目标接收者准备好就会立即返回

发送者将不会被阻塞,除非消息正在被接收者处理。根据你运行代码的机器的不同,接收者的goroutine可能会或者不会有足够的时间,在发送者继续执行前处理消息。

package main
import "fmt"
func main() {
ch := make(chan string)
go func() {
for m := range ch {
fmt.Println("processed:", m)
}
}()
ch <- "cmd.1"
ch <- "cmd.2" //won't be processed
}

运行结果:

processed: cmd.1
processed: cmd.2

32、向已关闭的Channel发送会引起Panic

从一个关闭的channel接收是安全的。在接收状态下的ok的返回值将被设置为false,这意味着没有数据被接收。如果你从一个有缓存的channel接收,你将会首先得到缓存的数据,一旦它为空,返回的ok值将变为false。

向关闭的channel中发送数据会引起panic。这个行为有文档说明,但对于新的Go开发者的直觉不同,他们可能希望发送行为与接收行为很像。

package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
for i := 0; i < 3; i++ {
go func(idx int) {
ch <- (idx + 1) * 2
}(i)
}
//get the first result
fmt.Println(<-ch)
close(ch) //not ok (you still have other senders)
//do other work
time.Sleep(2 * time.Second)
}

运行错误:

6
panic: send on closed channel
goroutine 6 [running]:
main.main.func1(0xc420070060, 0x1)

根据不同的应用,修复方法也将不同。可能是很小的代码修改,也可能需要修改应用的设计。无论是哪种方法,你都需要确保你的应用不会向关闭的channel中发送数据。

上面那个有bug的例子可以通过使用一个特殊的废弃的channel来向剩余的worker发送不再需要它们的结果的信号来修复。

package main
import (
"fmt"
"time"
)
func main() {
ch := make(chan int)
done := make(chan struct{})
for i := 0; i < 3; i++ {
go func(idx int) {
select {
case ch <- (idx + 1) * 2:
fmt.Println(idx, "sent result")
case <-done:
fmt.Println(idx, "exiting")
}
}(i)
}
//get first result
fmt.Println("result:", <-ch)
close(done)
//do other work
time.Sleep(3 * time.Second)
}

运行结果:

2 sent result
result: 6
1 exiting
0 exiting

33、使用"nil" Channels
在一个nil的channel上发送和接收操作会被永久阻塞。这个行为有详细的文档解释,但它对于新的Go开发者而言是个惊喜。​​​​​​​

package main
import (
"fmt"
"time"
)
func main() {
var ch chan int
for i := 0; i < 3; i++ {
go func(idx int) {
ch <- (idx + 1) * 2
}(i)
}
//get first result
fmt.Println("result:", <-ch)
//do other work
time.Sleep(2 * time.Second)
}

运行结果:

fatal error: all goroutines are asleep - deadlock!

这个行为可以在select声明中用于动态开启和关闭case代码块的方法。

package main
import "fmt"
import "time"
func main() {
inch := make(chan int)
outch := make(chan int)
go func() {
var in <-chan int = inch
var out chan<- int
var val int
for {
select {
case out <- val:
out = nil
in = inch
case val = <-in:
out = outch
in = nil
}
}
}()
go func() {
for r := range outch {
fmt.Println("result:", r)
}
}()
time.Sleep(0)
inch <- 1
inch <- 2
time.Sleep(3 * time.Second)
}

运行结果:

result: 1
result: 2

34、传值方法的接收者无法修改原有的值

方法的接收者就像常规的函数参数。如果声明为值,那么你的函数/方法得到的是接收者参数的拷贝。这意味着对接收者所做的修改将不会影响原有的值,除非接收者是一个map或者slice变量,而你更新了集合中的元素,或者你更新的域的接收者是指针。

package main
import "fmt"
type data struct {
num int
key *string
items map[string]bool
}
func (this *data) pmethod() {
this.num = 7
}
func (this data) vmethod() {
this.num = 8
*this.key = "v.key"
this.items["vmethod"] = true
}
func main() {
key := "key.1"
d := data{1, &key, make(map[string]bool)}
fmt.Printf("num=%v key=%v items=%v\n", d.num, *d.key, d.items)
//prints num=1 key=key.1 items=map[]
d.pmethod()
fmt.Printf("num=%v key=%v items=%v\n", d.num, *d.key, d.items)
//prints num=7 key=key.1 items=map[]
d.vmethod()
fmt.Printf("num=%v key=%v items=%v\n", d.num, *d.key, d.items)
//prints num=7 key=v.key items=map[vmethod:true]
}

运行结果:

num=1 key=key.1 items=map[]
num=7 key=key.1 items=map[]
num=7 key=v.key items=map[vmethod:true]

35、关闭HTTP的响应
当你使用标准http库发起请求时,你得到一个http的响应变量。如果你不读取响应主体,你依旧需要关闭它。注意对于空的响应你也一定要这么做。对于新的Go开发者而言,这个很容易就会忘掉。

一些新的Go开发者确实尝试关闭响应主体,但他们在错误的地方做

package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
resp, err := http.Get("http://www.oldboygo.cn/")
// resp, err := http.Get("http://www.baidu.cn/")
defer resp.Body.Close() //not ok
if err != nil {
fmt.Println(err)
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}

这段代码对于成功的请求没问题,但如果http的请求失败,resp变量可能会是nil,这将导致一个runtime panic。

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x40 pc=0x11eb322]

最常见的关闭响应主体的方法是在http响应的错误检查后调用defer。

package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
// resp, err := http.Get("https://api.ipify.org?format=json")
resp, err := http.Get("https://www.oldboygo.com")
if err != nil {
fmt.Println(err)
return
}
defer resp.Body.Close() //ok, most of the time :-)
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}

运行结果:

Get https://www.oldboygo.com: dial tcp: lookup www.oldboygo.com: no such host

大多数情况下,当你的http响应失败时,resp变量将为nil,而err变量将是non-nil。然而,当你得到一个重定向的错误时,两个变量都将是non-nil。这意味着你最后依然会内存泄露。

通过在http响应错误处理中添加一个关闭non-nil响应主体的的调用来修复这个问题。另一个方法是使用一个defer调用来关闭所有失败和成功的请求的响应主体。

package main
import (
"fmt"
"io/ioutil"
"net/http"
)
func main() {
resp, err := http.Get("https://api.ipify.org?format=json")
if resp != nil {
defer resp.Body.Close()
}
if err != nil {
fmt.Println(err)
return
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
fmt.Println(err)
return
}
fmt.Println(string(body))
}

resp.Body.Close()的原始实现也会读取并丢弃剩余的响应主体数据。这确保了http的链接在keepalive http连接行为开启的情况下,可以被另一个请求复用。最新的http客户端的行为是不同的。现在读取并丢弃剩余的响应数据是你的职责。如果你不这么做,http的连接可能会关闭,而无法被重用。

如果http连接的重用对你的应用很重要,你可能需要在响应处理逻辑的后面添加像下面的代码:

_, err = io.Copy(ioutil.Discard, resp.Body)

如果你不立即读取整个响应将是必要的,这可能在你处理json API响应时会发生:

json.NewDecoder(resp.Body).Decode(&data)

标签:总结,http,错误,err,fmt,Println,func,Go,main
From: https://blog.51cto.com/u_13940603/5886341

相关文章

  • Go语言错误总结(四)
    22、内建的数据结构操作不是同步的即使Go本身有很多特性来支持并发,并发安全的数据集合并不是其中之一,确保数据集合以原子的方式更新是你的职责。Goroutines和channels是实现......
  • Go 操作 MySQL 数据库
    这一期讲一讲如何使用Go操作MySQL数据库,这里就不讲MySQL的安装以及配置了,但要记得开启MySQL服务,我这里使用的是MySQL8.0.20版本。加载数据库驱动想要连接到数据......
  • Go 的 MySQL 预处理、MySQL 事务
    预处理是什么在普通SQL语句执行过程中,客户端会对SQL语句进行占位符替换,从而得到要执行的完整SQL语句,客户端再将此SQL语句发送到服务端执行,服务端最后把结果返回给客......
  • go实现投票并实时打印投票信息
    packagemainimport"fmt"varstudents[]Studentvarflagbool=truetypeStudentstruct{noint//候选人编号namestring//候选热姓名countint//得票数}fun......
  • 咕噜校园项目总结
    咕噜校园项目功能解刨技术分析校园后台:正方软件后台系统抓包登录:抓取账号密码,看看加密各方面查看请求参数成功和失败动作课表查询抓取URL查看请求参数解......
  • 4 django_Adimn账户
    1.创建管理员账号退出服务器输入命令pythonmanage.pycreatesuperuser启动服务器pythonmanage.pyrunserver输入网址:http://127.0.0.1:8000/admin/logi......
  • 二分查找总结
    二分查找二分查找也常被称为二分法或者折半查找(BinarySearch),每次查找时通过将待查找区间分成两部分并只取一部分继续查找,将查找的复杂度大大减少。对于一个长度为O(n) ......
  • 错误 CS1617 Invalid option '7.3' for /langversion; must be ISO-1, ISO-2, Default
    一年前的程序版本拉下来需要改动下,突然生成程序报这个错。解决方法,网上说修改项目属性-生成-高级,语言版本选择default。我这个项目就不能修改,后来查了下https://stac......
  • GO mod not found
    Goget下载包的时候,会报错:go.modfilenotfoundincurrentdirectoryoranyparentdirectory.解决方法:初始化项目,比如我的项目名称叫GOWORKSPACEPSD:\goWorkSp......
  • Go语言开发环境搭建
    1go下载安装地址:https://golang.google.cn/dl/安装路径自己选一个,比如D:/go,qita安装都选下一步即可安装完把D:/go/bin添加到系统环境变量,就可以了,在CMD中输入......