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++
}
相关推荐
㳺三才人子6 小时前
初探 Flask
后端·python·flask·html
星栈独行6 小时前
我在 Rust 全栈项目里用 JWT 做无状态认证
开发语言·后端·rust·前端框架·开源·github·web
Java爱好狂.6 小时前
Java程序员体系化学习路线(2026最新版)
java·后端·java面试·java架构师·java程序员·java八股文·java学习路线
陈随易6 小时前
Redis 8.8发布,一定要更新
前端·后端·程序员
装不满的克莱因瓶7 小时前
SpringBoot 如何将 lib 目录中jar包打包进最终的jar包里面
spring boot·后端·maven·jar·mvn
ltl7 小时前
Transformer 原论文实验结果:为什么 28.4 BLEU 足以改写路线图
后端
excel8 小时前
为什么我推荐使用 Termius:现代 SSH 工具的完整体验
前端·后端
卷毛的技术笔记9 小时前
Java后端硬核实战:用Spring AI Alibaba+Redis给LLM装上“超强记忆中枢”
java·人工智能·redis·后端·spring·ai·系统架构
IT_陈寒10 小时前
Java的Optional差点让我掉坑里,这几个坑你别踩
前端·人工智能·后端
子兮曰10 小时前
Harness 驾驭工程深度教程:从 AGENTS.md 到全链路 AI 编码基础设施
前端·后端·ai编程