Go语言开发小技巧&易错点100例(十四)

2024-04-17 23:34:07 浏览数 (1)

往期回顾:

  • Go语言开发小技巧&易错点100例(一)
  • Go语言开发小技巧&易错点100例(二)
  • Go语言开发小技巧&易错点100例(三)
  • Go语言开发小技巧&易错点100例(四)
  • Go语言开发小技巧&易错点100例(五)
  • Go语言开发小技巧&易错点100例(六)
  • Go语言开发小技巧&易错点100例(七)
  • Go语言开发小技巧&易错点100例(八)
  • Go语言开发小技巧&易错点100例(九)
  • Go语言开发小技巧&易错点100例(十)
  • Go语言开发小技巧&易错点100例(十一)
  • Go语言开发小技巧&易错点100例(十二)
  • Go语言开发小技巧&易错点100例(十三)

本期看点(技巧类用【技】表示,易错点用【易】表示)

  • init函数的执行机制【易】
  • sync.Once同步【技】
  • Go进行小数的运算【技】

正文开始:

init()函数的执行机制

在Go语言中,init函数是用于初始化包或模块的特殊函数。它们会在程序开始执行任何用户定义的函数之前自动调用。Go语言运行时系统会保证在程序开始执行main函数之前,所有包的init函数都会被调用。但是,具体的调用顺序依赖于包之间的依赖关系。

  • 如果包A依赖于包B(例如,包A导入了包B),那么包B的init函数会在包A的init函数之前执行。
  • 如果包A和包B都不依赖于对方,但是它们都被main包所依赖,那么它们的init函数将按照编译器的内部顺序执行,这通常与它们在文件系统中的顺序有关,但并不严格保证。

我们来做一下试验:

main包

代码语言:go复制
package main

import (
	"fmt"
	"go-init/db"
)

func init() {
	fmt.Println("package main init() func ...")
}

func main() {
	db.LoadModel() //顺序调换还是限制性另一个文件的init函数
	db.LoadConfig()
	fmt.Println("package main main() func ...")
}

db包config.go:

代码语言:go复制
package db

import "fmt"

func init() {
	fmt.Println("package db init() func ...")
}

func LoadConfig() {
	fmt.Println("package db LoadConfig() func ...")
}

db包model.go:

代码语言:go复制
package db

import "fmt"

func init() {
	fmt.Println("package db other init() func ...")
}

func LoadModel() {
	fmt.Println("package db LoadModel() func ...")
}

执行main函数:

代码语言:shell复制
package db init() func ...
package db other init() func ...
package main init() func ...
package db LoadModel() func ...
package db LoadConfig() func ...
package main main() func ...

由上面的代码我们可知,优先级关系为:依赖包的init函数 > 其他包的init函数 > main包的init函数 > main函数

总的来说,虽然你不能直接控制init函数的执行顺序,但你可以通过控制包的依赖关系来间接地影响它们。如果需要对初始化顺序有严格的控制,通常最好的做法是使用显式的初始化函数,并在main函数中或在某个特定init函数中调用它们,以确保正确的执行顺序。

sync.Once同步

在Go语言中,sync.Once 是一个用于确保某个操作只执行一次的同步原语。它提供了一种机制,可以在多线程或多协程环境下安全地执行初始化操作,而无需担心重复执行或竞态条件。

sync.Once 结构体有一个未导出的字段,用于跟踪操作是否已经被执行过。它提供了一个 Do 方法,该方法接受一个函数作为参数,并确保该函数只被调用一次,不论 Do 被调用多少次。

下面是 sync.Once 的基本用法:

代码语言:go复制
package main

import (
	"fmt"
	"sync"
)

func main() {
	var once sync.Once

	// 定义一个初始化函数
	initFunc := func() {
		fmt.Println("Initialization function is called only once.")
	}

	// 模拟多个协程尝试调用初始化函数
	for i := 0; i < 5; i   {
		go once.Do(initFunc)
	}

	// 等待所有协程执行完毕
	var wg sync.WaitGroup
	wg.Add(5)
	for i := 0; i < 5; i   {
		go func() {
			defer wg.Done()
			once.Do(initFunc)
		}()
	}
	wg.Wait()
}

在上面的例子中,尽管我们多次调用 once.Do(initFunc),但 initFunc 函数只会被执行一次。这是通过 sync.Once 的内部同步机制实现的,它确保了 Do 方法内部的函数只会在第一次调用时执行。

sync.Once 在多种场景下都非常有用,尤其是在需要执行一些只需要初始化一次的资源加载、配置设置或全局状态设置时。由于它保证了操作的原子性,因此在并发编程中特别有用。

需要注意的是,sync.Once 只能确保某个操作执行一次,而不是确保某个状态只被设置一次。如果你需要在多次调用之间保持某个状态,你可能需要使用其他同步机制,比如互斥锁(sync.Mutex)或原子操作(sync/atomic 包)。

Go进行保留小数的运算

有时候在项目中涉及到Go语言保留小数运算的时候经常会弄不清,比如两个int32类型的值进行除法保留小数或者具体到保留两位小数,int32类型和float32类型进行乘除保留小数等,下面有几个简单的例子,展示了如何在Go中进行小数运算:

两个float64类型进行运算:

代码语言:go复制
package main

import (
	"fmt"
	"math"
)

func main() {
	// 定义两个float64类型的小数
	a := 3.14
	b := 2.71

	// 乘法
	product := a * b
	fmt.Printf("Product: %fn", product)

	// 除法
	quotient := a / b
	fmt.Printf("Quotient: %fn", quotient)
}

有两个int32类型进行除法运算,同时保留两位小数:

代码语言:go复制
package main

import (
	"fmt"
)

func main() {
	// 定义两个int32类型的变量
	a := int32(10)
	b := int32(3)

	// 将它们转换为float64类型以执行除法运算
	result := float64(a) / float64(b)

	// 使用fmt.Printf格式化输出,保留两位小数
	fmt.Printf("Result: %.2fn", result)
}

一个int32类型的变量和一个float32类型的变量除法运算同时保留两位小数:

代码语言:go复制
package main

import (
	"fmt"
)

func main() {
	// 定义一个int32类型的变量和一个float32类型的变量
	a := int32(10)
	b := float32(3.0)

	// 将int32变量转换为float32类型以执行除法运算
	result := float32(a) / b

	// 使用fmt.Printf格式化输出,保留两位小数
	fmt.Printf("Result: %.2fn", float64(result)) // 注意:将float32转换为float64以正确格式化输出
}

完结~

我正在参与2024腾讯技术创作特训营最新征文,快来和我瓜分大奖!

0 人点赞