Golang 学习笔记08-线程安全和异常处理

线程安全和sync.Map

假设一个加法和一个减法的协程

package main

import (
    "fmt"
    "sync"
)

var sum int

var wait sync.WaitGroup

func add() {
    for i := 0; i < 1000000; i++ {
       sum++
    }
    wait.Done()
}
func sub() {
    for i := 0; i < 1000000; i++ {
       sum--
    }
    wait.Done()
}
func main() {
    wait.Add(2)
    go add()
    go sub()
    wait.Wait()
    fmt.Println(sum)
}

在预想中,输出的sum值应该是0,但实际不是,每次都不同,无法预测

解决方案:

var lock sync.Mutex
func adds() {
	lock.Lock()
	for i := 0; i < 1000000; i++ {
		sums++
	}
	lock.Unlock()
	waits.Done()
}
func subs() {
	lock.Lock()
	for i := 0; i < 1000000; i++ {
		sums--
	}
	lock.Unlock()
	waits.Done()
}

线程不安全的map,同一时刻在执行读和写

package main

import "fmt"

var maps = map[int]string{}

func main() {
	go func() {
		for {
			maps[1] = "张三"
		}
	}()
	go func() {
		for {
			fmt.Println(maps[1])
		}
	}()
	select {}
}
fatal error: concurrent map read and map write

解决方案

var maps1 = sync.Map{}

func main() {
	go func() {
		for {
			maps1.Store(1, "张三")
		}
	}()
	go func() {
		for {
			val, ok := maps1.Load(1)
			fmt.Println(val, ok)
		}
	}()
	select {}
}

异常处理

常见的异常处理

  • 向上抛:将错误交给上一级处理

    一般是用于框架层,有些错误框架层面不能擅做决定,将错误向上抛不失为一个好的办法

  • 中断程序:遇到错误直接停止程序

    这种一般是用于初始化,一旦初始化出现错误,程序继续走下去也意义不大了,还不如中断掉

  • 恢复程序:我们可以在一个函数里面,使用一个defer,可以实现对panic的捕获

    以至于出现错误不至于让程序直接崩溃

    这种一般也是框架层的异常处理所做的

向上抛

package main

import (
    "errors"
    "fmt"
)

// 底层函数
func div(a, b int) (res int, err error) {
    if b == 0 {
       err = errors.New("除数不能为0")
       return
    }
    res = a / b
    return
}

// 中层函数
func server() (res int, err error) {
    res, err = div(10, 0)
    if err != nil {
       // 把错误向上传递
       return
    }
    // 执行其他的逻辑
    res++
    res += 2
    return
}
func main() {
    res, err := server()
    if err != nil {
       fmt.Println(err)
       return
    }
    fmt.Println(res)
}

中断程序

func init() {
	_, err := os.ReadFile("1.txt")
	fmt.Println(err)
}

func main() {
	fmt.Println("main")
}
open 1.txt: The system cannot find the file specified.
main

以上的情况并没有对错误处理,依然走到了main

两种处理方式:panic(),log.Fatalln("")

func init() {
	_, err := os.ReadFile("1.txt")
	fmt.Println(err)
	if err != nil {
		//panic(err)
		log.Fatalln("发生错误")
	}
}
open 1.txt: The system cannot find the file specified.
2026/02/08 18:43:10 发生错误
func init() {
	_, err := os.ReadFile("1.txt")
	fmt.Println(err)
	if err != nil {
		panic("发生错误")
		//log.Fatalln("发生错误")
	}
}
open 1.txt: The system cannot find the file specified.
panic: 发生错误

goroutine 1 [running]:
main.init.0()
	D:/project/go_study/study/21.异常处理/2.中断程序.go:13 +0x8b

异常捕获

如果函数发生错误会导致后面的正常逻辑代码不再继续运行

package main

import "fmt"

func read() {
	var list []int = []int{1, 2}
	fmt.Println(list[3])
}

func main() {
	read()

	//	正常的逻辑代码
	fmt.Println("正常逻辑代码")
}
panic: runtime error: index out of range [3] with length 2

解决方案:

func read() {
	defer func() {
		fmt.Println("defer")
		err := recover()
		if err != nil {
			fmt.Println(err)
		}
	}()
	var list []int = []int{1, 2}
	fmt.Println(list[3])
}

func main() {
	read()

	//	正常的逻辑代码
	fmt.Println("正常逻辑代码")
}
defer
runtime error: index out of range [3] with length 2
正常逻辑代码

以上的写法会不知道错误的位置

func read() {
	defer func() {
		fmt.Println("defer")
		err := recover()
		if err != nil {
			fmt.Println(err)
			//	调用堆栈
			fmt.Println(string(debug.Stack()))
		}
	}()
	var list []int = []int{1, 2}
	fmt.Println(list[3])
}
defer
runtime error: index out of range [3] with length 2
goroutine 1 [running]:
runtime/debug.Stack()
	D:/Program Files/go1.25.5.windows-amd64/go/src/runtime/debug/stack.go:26 +0x5e
main.read.func1()
	D:/project/go_study/study/21.异常处理/3.异常捕获.go:15 +0x8a
panic({0x7ff760bac060?, 0xc0000140d8?})
	D:/Program Files/go1.25.5.windows-amd64/go/src/runtime/panic.go:783 +0x132
main.read()
	D:/project/go_study/study/21.异常处理/3.异常捕获.go:19 +0x3a
main.main()
	D:/project/go_study/study/21.异常处理/3.异常捕获.go:23 +0x13

正常逻辑代码

这个用于捕获异常的defer的延迟函数可以在调用链路上的任何一个函数上

一般用于在最上层函数,捕获所有异常