Go踩坑记录分享

废话不多说直接上使用错误使用示例

示例1

有时我们为了避免切片底层数据扩展带来的开销,会提前指定make的cap大小

func Example1() {
	persons := make([]*Person, 1000)
	for i := 0; i < 10000; i++ {
		persons = append(persons, &Person{
			Name: "example1",
			Age:  i,
		})
	}
}

// 正确方法:声明lens为0,就可以使用append将item逐一加到切片中
type Person struct {
	Name string
	Age int
}

func Example1() {
	persons := make([]*Person, 0, 1000)

	for i := 0; i < 1000; i++ {
		persons = append(persons, &Person{
			Name: "example1",
			Age: i,
		})
	}
}

// 正确方法2:使用切片逐个赋值的方式
type Person struct {
	Name string
	Age int
}

func Example1() {
	persons := make([]*Person, 1000)

	for i := 0; i < 1000; i++ {
		persons[i] =  &Person{
			Name: "example1",
			Age: i,
		}
	}
}

错误分析

虽然我们指定了make的cap大小,但是我们并没有指定make的length字段的大小,这个时候length字段的大小等于cap的大小,参照下面切片用法

补充:切片正确初始化、创建方法

使用 Golang 内置的 make() 函数创建切片,此时需要传入一个参数来指定切片的长度:

// 创建一个整型切片
// 其长度和容量都是 5 个元素
slice := make([]int, 5)

此时只指定了切片的长度,那么切片的容量和长度相等。也可以分别指定长度和容量:

// 创建一个整型切片
// 其长度为 3 个元素,容量为 5 个元素
slice := make([]int, 3, 5)

分别指定长度和容量时,创建的切片,底层数组的长度是指定的容量,但是初始化后并不能
访问所有的数组元素。 

示例2

1. 向nil map中添加元素导致panic

func Example2() {
	var m1 map[string]string

	m1["a"] = "b"
  …
}

// 解决方案:给map初始化

2. 函数返回值为map时,在使用前也应该初始化,否则会panic

func Example3() (m1 map[string]string) {
	m1["a"] = "b"
	return m1
}
// 解决方案:给map初始化

3. map有嵌套,嵌套的map也要初始化,否则会panic

func Example2() {
	m1 := make(map[string]map[int]int)
	m1["go"][0] = 0
}

// 正确方法
// 先初始化,再使用
func Example6() {
	m1 := make(map[string]map[int]int)
	m1["go"] = make(map[int]int)
	m1["go"][0] = 0
}

示例3

对于带for循环的select,break并不能打破for循环,会永远循环下去

func Example3(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			break
		default:
			...
		}
	}
}

正确方法1:采用break + 标签
func Example3(ctx context.Context) {
	//LOOP 是标签
	LOOP:
	for {
		select {
		case <-ctx.Done():
			break LOOP
		default:
			...
		}
	}
}

正确方法2:采用goto + 标签
func Example3(ctx context.Context) {
	for {
		select {
		case <-ctx.Done():
			goto LOOP
		default:
			...
		}
	}
  //END 是标签
	END:
}

示例4

当panic发生后,虽然有recover,但是并没有将error信息传递到上层,可能会导致后续逻辑异常

func Example4(ctx context.Context) {
	err := DoExample4(ctx)
	if err != nil {
		return
	}
	...
}

func DoExample4() error {
	defer func(ctx context.Context) {
		if err := recover(); err != nil {
			log.ErrorContextf(ctx, "panic: %v", err)
		}
	}()
	
	panic("DoExample4")
}

// 正确做法:及时处理返回异常信息
func Example4(ctx context.Context) {
	err := DoExample4(ctx)
	if err != nil {
		return
	}
	...
}

func DoExample4() (err error) {
	defer func() {
		if panicErr := recover(); panicErr != nil {
			err = fmt.Errorf("panic %v", panicErr)
		}
	}()

	panic("DoExample4")
}


示例5

Go协程启动的函数,没有recover,painc可能导致整个程序panic

func Example5() {
	go func() {
		doSomething()
	}()
}

func doSomething() {
	println("this is a good day!")
	panic("but it panic")
}

// 正确方法:Go协程启动的函数,一定要有recover方法
func Example5() {
	go func() {
		defer func() {
			// 捕获错误并做相应的处理
			if err := recover(); err != nil {
				println("the program is err = %s", err)
			}
		}()
		doSomething()
	}()
}

func doSomething() {
	println("this is a good day!")
	panic("but it panic")
}

示例6

Go协程中,采用闭包的形式使用请求传入的context,可能会导致context canceled错误

func Example6(ctx context.Context) {
	go func() {
		defer func() {
			if err := recover(); err != nil {
				...
			}
		}()

		doSomething(ctx)
	}()
}

func doSomething(ctx context.Context) {
	...
}


// 解决方法:我们可以克隆一个context,例如使用trpc包中提供多个CloneContext方法
func Example6(ctx context.Context) {
	go func(ctx context.Context) {
		defer func() {
			if err := recover(); err != nil {
				...
			}
		}()

		doSomething(ctx)
	}(trpc.CloneContext(ctx))
}

func doSomething(ctx context.Context) {
	...
}

示例7

使用gorm查询时,用Take, First, Last、Find函数查询数据库时,当传入函数的参结果集的变量只能为Struct类型或Slice类型,当传入变量为Struc类型时,如果检索出来的数据为0条,会抛出ErrRecordNotFound错误,当传入变量为Slice类型时,任何条件下均不会抛出ErrRecordNotFound错误

具体分析:GORM之ErrRecordNotFound采坑记录 - 掘金

// 查不到数据返回ErrRecordNotFound
func Example7(db *gorm.DB) error {
	person := model.Person{}
	return db.Find(&person, -1).Error
}


// 查不到数据返回nil
func Example7(db *gorm.DB) error {
	person := []model.Person{}
	return db.Find(&person, -1).Error
}

示例8

当gorm更新使用struct时,会有零值更新问题,就是对于零值字段,会忽略更新

type Person struct {
	Name string
	Age int
}

// 更新数据库时忽略结构体中为零值的字段
db.Model(&person).Updates(Person{Name: "test", Age: 0})


// 解决方法:使用map可以不忽略零值字段
// 当然也可以在定义数据时避免使用零值字段
type Person struct {
	Name string
	Age int
}


db.Model(&person).Updates(map[string]interface{}{“Name": "test", "Age": 0})

示例9

闭包使用for…range声明的变量,导致非预期结果

本质原因是for…range中声明的变量其实只有一个地址,每次循环都是赋值给同一个地址

func Example9() {
	s := []string{"a", "b", "c"}
	for _, v := range s {
		go func() {
			fmt.Println(v)
		}()
	}
	select {}
}

// 正确方法1:通过复制变量,避免地址操作带来的影响
func Example9() {
	s := []string{"a", "b", "c"}
	for _, v := range s {
		v1 := v
		go func() {
			fmt.Println(v1)
		}()
	}
	select {}
}


// 正确方法2:通过函数传参深拷贝来解决,地址操作带来的影响
func Example9() {
	s := []string{"a", "b", "c"}
	for _, v := range s {
		v1 := v
		go func() {
			fmt.Println(v1)
		}()
	}
	select {}
}

示例10

map并发读写,会直接panic掉

func Example10() {
   m := make(map[int]int)
   go func() {
      for i := 0; i < 10000; i++ {
         m[i] = i
      }
   }()
   go func() {
      for i := 0; i < 10000; i++ {
         fmt.Println(m[i])
      }
   }()
   time.Sleep(time.Second * 5)   // fatal error: concurrent map read and map write
}


解决方法:
加锁
sync.Map
改成协程内局部变量

示例11

slice并发写不会出现panic,能正常跑,但数据通常不是预期结果

func Example11() {
   var arr []int
   for i := 0; i < 1000; i++ {
      go func(v int) {
         arr = append(arr, v)
      }(i)
   }
   time.Sleep(time.Second)
   for i, v := range arr {
      fmt.Println(i, ": ", v) // 数量,非预期的1000个
   }
}


解决方法:加锁控制并发写入的并发覆盖问题
func Example11() {
	lock := sync.Mutex{}
	var arr []int
	for i := 0; i < 1000; i++ {
		go func(v int) {
			lock.Lock()
			arr = append(arr, v)
			lock.Unlock()
		}(i)
	}
	time.Sleep(time.Second)
	for i, v := range arr {
		fmt.Println(i, ": ", v) // 数量预1000个
	}
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
记录Windows平台用户的登录和注销时间,可以使用`Win32 API`。`Win32 API`是Windows操作系统中可用的一组应用程序接口,可以让开发人员编写Windows应用程序。以下是在Go中记录Windows平台用户登录和注销时间的一些步骤: 1. 导入必要的包: ```go import ( "fmt" "syscall" "time" "unsafe" ) ``` 2. 定义必要的常量和结构体: ```go const ( EVENT_SYSTEM_FOREGROUND = 0x0003 WINEVENT_OUTOFCONTEXT = 0x0000 ) type LASTINPUTINFO struct { cbSize uint32 dwTime uint32 } ``` 3. 创建`Win32 API`的`SetWinEventHook`函数来监视事件: ```go user32 := syscall.NewLazyDLL("user32.dll") procSetWinEventHook := user32.NewProc("SetWinEventHook") procUnhookWinEvent := user32.NewProc("UnhookWinEvent") hwnd := syscall.Handle(0) id := syscall.NewCallback(func(hWinEventHook syscall.Handle, event uint32, hwnd syscall.Handle, idObject int32, idChild int32, dwEventThread uint32, dwmsEventTime uint32) { switch event { case EVENT_SYSTEM_FOREGROUND: fmt.Println("User switched to window", hwnd) // 在这里记录用户登录时间 } }) hook, _, _ := procSetWinEventHook.Call( uintptr(EVENT_SYSTEM_FOREGROUND), uintptr(EVENT_SYSTEM_FOREGROUND), uintptr(0), id, uintptr(0), uintptr(0), uintptr(WINEVENT_OUTOFCONTEXT), ) ``` 4. 记录用户登录时间。可以使用`GetLastInputInfo`函数获取系统上一次用户输入的时间: ```go kernel32 := syscall.NewLazyDLL("kernel32.dll") procGetLastInputInfo := kernel32.NewProc("GetLastInputInfo") lii := LASTINPUTINFO{} lii.cbSize = uint32(unsafe.Sizeof(lii)) procGetLastInputInfo.Call(uintptr(unsafe.Pointer(&lii))) lastInput := time.Duration(lii.dwTime) * time.Millisecond now := time.Now() idleTime := now.Sub(lastInput) fmt.Println("User idle for", idleTime) // 在这里记录用户注销时间 ``` 5. 最后,记得在程序退出时取消事件钩子: ```go procUnhookWinEvent.Call(hook) ``` 这样,您就可以使用Go记录Windows平台用户的登录和注销时间了。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值