likes
comments
collection
share

GO 实践|编程时常会犯下错误

作者站长头像
站长
· 阅读数 1

整理汇总下编程中你也有可能犯下的一些错误行为:

错误没有处理

Go 不像其他语言能够异常捕获而是通过将错误作为值进行返回。刚开始接触 golang 有时候就是会忽略有些错误没有将错误返回。所以在执行可能失败的操作后,都必须检查错误。

// 错误示例
func FileRead(path string) []byte {
    data, _ := ioutil.ReadFile(path)
    return data
}

// 正确示例
func FileRead(path string) ([]byte,error) {
    data, err := ioutil.ReadFile(path)
    if err != nil {
        return nil, err
    }
    return data,err
}

协程 Goroutine和 Channel 的错误使用

不正确使用协程的通道可能会导致死锁,就是在运行时如果没有控制好容易导致多个协程协程之间发生互相等待情况从而导致死锁的情况。

比如下面的情况,主线程将数据发送到通道中,但没有其他的协程Goroutine可以从该通道接收,这样就很容易导致死锁。

//错误示例,执行结果报错:fatal error: all goroutines are asleep - deadlock!
func main(){
    ch :=make(chan int)
    ch <- 1;
    fmt.Println(<-ch)
}

// 正确示例
func main() {
	ch := make(chan int)
	go func() {
		ch <- 1
	}()

	fmt.Println(<-ch)
}

Go 中谈到不要通过共享内存来进行通信,而是通过通信进行共享内存。不同的协程 goroutine通过 channel 交换任意资源,实现不同协程之间的数据同步。其重要点是,channel 是用来在不同的 goroutine 中交换数据。是在不同协程,而是同一个协程中。所以在错误示例中执行会出现死锁。因为没有其他的协程进行接收。

错误使用 Interface

type Machine interface {
	DoWork() string
}
type Worker struct{}

func (w Worker) StartWork() string {
	return "success"
}

func StartMachine(worker Machine) {
	fmt.Println(worker.DoWork())
}

func main() {
	w := Worker{}
	StartMachine(w)
}

其中 Worker 没有实现Machine 接口的方法就会在编译的时候报错。正确的方式是Worker 应该实现Dowork 方法。在go中,当一个类型完全实现接口的所有方法且方法的签名都是一致时,结构体才实现接口。正确示例如下

type Machine interface {
	DoWork() string
}
type Worker struct{}

func (w Worker) DoWork() string {
	return "success"
}

func StartMachine(w Machine) {
	fmt.Println(w.DoWork())
}

没有使用 defer

在对一些文件句柄、网络连接或互斥锁资源的管理,常常需要手动进行资源清理或释放,有时候就会发生问题,比如读取一个文本后先打开文本,读取结束后就需要关闭文本。

func ReadFile(fileName string) {
	f, err := os.Open(fileName)
	if err != nil {
		log.Fatal(err)
	}
	f.Close() // 错误方式
    defer f.Close() // 正确方式
}

如果在 close 之前发生err了,就会导致file 还是报错open的状态,从而导致资源泄露。使用 defer的化,无论是否有异常发生,都在该方法结束后自动执行 f.close。这样就可以确保了文件最终状态都是被关闭的,防止了资源泄露的情况。

忽略并发资源

多个协程的对同一资源的访问如果没有正确的在各个协程之间同步好就会导致竞争条件,得到的结果就会时错误的。多个协程一般都是并发的,他们执行顺序都是不一定。

func addNum()  {
	curNum++
}
var curNum int
func main() {
	 for i:=0;i<1000;i++ {
		 go addNum()
	 }
	 time.Sleep(time.Second)
	 fmt.Println(curNum)
}

以上代码执行结果永远都是小于1000的;go 启动异步协程,每个协程都有权限修改 curnum 的值,有可能发生上一个协程值还未修改完另一个协程就先修改了。对于共享资源可以使用锁控制并发时对共享资源的 访问,保证一次只能一个协程访问curNum,从而防止出现竞争情况。常用的方式就是使用 sync.Mutex.

var mu sync.Mutex
func addNum() {
	mu.Lock()
	defer mu.Unlock()
	curNum++
}