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

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

错误没有处理

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

go 复制代码
// 错误示例
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可以从该通道接收,这样就很容易导致死锁。

go 复制代码
//错误示例,执行结果报错: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

go 复制代码
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中,当一个类型完全实现接口的所有方法且方法的签名都是一致时,结构体才实现接口。正确示例如下

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

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

go 复制代码
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。这样就可以确保了文件最终状态都是被关闭的,防止了资源泄露的情况。

忽略并发资源

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

css 复制代码
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.

go 复制代码
var mu sync.Mutex
func addNum() {
	mu.Lock()
	defer mu.Unlock()
	curNum++
}
相关推荐
bing_1582 小时前
简单工厂模式 (Simple Factory Pattern) 在Spring Boot 中的应用
spring boot·后端·简单工厂模式
天上掉下来个程小白3 小时前
案例-14.文件上传-简介
数据库·spring boot·后端·mybatis·状态模式
Asthenia04123 小时前
基于Jackson注解的JSON工具封装与Redis集成实战
后端
编程星空4 小时前
css主题色修改后会多出一个css吗?css怎么定义变量?
开发语言·后端·rust
程序员侠客行4 小时前
Spring事务原理 二
java·后端·spring
dmy5 小时前
docker 快速构建开发环境
后端·docker·容器
sjsjsbbsbsn5 小时前
Spring Boot定时任务原理
java·spring boot·后端
计算机毕设指导65 小时前
基于Springboot学生宿舍水电信息管理系统【附源码】
java·spring boot·后端·mysql·spring·tomcat·maven
计算机-秋大田6 小时前
基于Spring Boot的兴顺物流管理系统设计与实现(LW+源码+讲解)
java·vue.js·spring boot·后端·spring·课程设计
羊小猪~~7 小时前
MYSQL学习笔记(九):MYSQL表的“增删改查”
数据库·笔记·后端·sql·学习·mysql·考研