golang中的init初始化函数
0.1、索引
https://waterflow.link/articles/1666090530880
1、概念
1.1、源文件里的代码执行顺序
init 函数是用于初始化应用程序状态的函数。 它不接受任何参数并且不返回任何结果(一个 func() 函数)。 初始化包时,将初始化包中的所有常量和变量声明。 然后,执行初始化函数。 下面是一个初始化主包的例子:
package main
import "fmt"
// 1
var a = func() int {
fmt.Println("var a")
return 0
}()
// 2
func init() {
fmt.Println("init")
}
// 1
var b = func() int {
fmt.Println("var b")
return 1
}()
// 3
func main() {
fmt.Println("main")
}
上面代码的初始化顺序是:
- 初始化常量/变量(虽然b在init函数后面,但是会首先初始化)
- 初始化init函数
- 执行main函数
我们看下打印的结果:
go run 2.go
var a
var b
init
main
1.2、不同包的init函数执行顺序
初始化包时会执行一个 init 函数。 在下面的例子中,我们定义了两个包,main 和 redis,其中 main 依赖于 redis。 首先, 2 .go 是主包:
package main
import (
"fmt"
"go-demo/100gomistakes/2/redis"
)
// 2
func init() {
fmt.Println("main init")
}
// 3
func main() {
err := redis.Store("ni", "hao")
fmt.Println(err)
}
我们可以看到main包中调用了redis包的方法。
我们再看下redis包中的内容:
package redis
import "fmt"
// 1
func init() {
fmt.Println("redis init")
}
func Store(key, value string) error {
return nil
}
因为main依赖redis,所以先执行redis包的init函数,然后是main包的init,然后是main函数本身。上面的代码中标明了执行顺序。
1.3、同一个包不同文件init执行顺序
我们可以为每个包定义多个初始化函数。 当我们这样做时,包内的 init 函数的执行顺序是基于源文件的字母顺序。 例如,如果一个包包含一个 a.go 文件和一个 b.go 文件,并且都有一个 init 函数,则首先执行 a.go init 函数。
但是如果我们把文件a.go改为ca.go,则会先执行b.go的init函数。
所以我们不应该依赖包中初始化函数的顺序。 实际上,这可能很危险,因为可以重命名源文件,从而可能影响执行顺序。
下图说明了这个问题:

1.4、同一个文件中的init函数
当然,我们还可以在同一个源文件中定义多个初始化函数:
package main
import (
"fmt"
"go-demo/100gomistakes/2/redis"
)
func init() {
fmt.Println("main init1")
}
func init() {
fmt.Println("main init2")
}
func main() {
err := redis.Store("ni", "hao")
fmt.Println(err)
}
执行顺序是按照源文件顺序执行的,我们看下打印的结果:
go run 2.go
redis2 init
redis init
main init1
main init2
<nil>
1.5、以副作用方式执行的init函数
我们开发的时候经常会使用gorm查询数据库,所以我们经常会看到在初始化db连接的时候会有下面的代码:
package models
import (
_ "github.com/go-sql-driver/mysql"
"github.com/jinzhu/gorm"
)
这种情况就是说,我们没有强依赖mysql包(没有直接使用mysql的公共函数)。但是我们需要初始化mysql包里的一些数据,也就是执行mysql包里的init函数。这个时候我们就可以在这个包的前面加上_。
需要注意的是init函数不能被当作普通函数调用,会编译报错。
2、使用场景
首先,让我们看一个使用 init 函数可能被认为不合适的示例:持有数据库连接池。 在示例的 init 函数中,我们使用 sql.Open 打开一个数据库。 这个全局变量db会被其他函数调用:
package main
import (
"database/sql"
"fmt"
_ "github.com/go-sql-driver/mysql"
"log"
)
var db *sql.DB
func init() {
d, err := sql.Open("mysql",
"root:liufutian@tcp(127.0.0.1:3306)/test")
if err != nil {
log.Panic(err)
}
err := db.Ping()
if err != nil {
log.Panic(err)
}
db = d
}
func main() {
}
在本例中,我们打开数据库,检查是否可以 ping 通,然后将其分配给全局变量。
但是这种实现会带来一些问题:
- init 函数中的错误管理是有限的。 实际上,由于 init 函数不返回错误,因此发出错误信号的唯一方法之一就是panic,导致应用程序停止。 在我们的示例中,如果打开数据库失败,无论如何都会停止应用程序。 但是,不一定要由包本身来决定是否停止应用程序。 有可能调用者想实现重试或使用回退机制。 在这种情况下,在 init 函数中打开数据库会阻止客户端包实现其错误处理逻辑。
- 如果我们向这个文件添加测试,init 函数将在运行测试用例之前执行,但是我们想要的是创建db连接的时候需要测试。 因此,此示例中的 init 函数使编写单元测试变得复杂。
- 该示例需要将数据库连接池分配给全局变量。 全局变量有一些严重的缺点; 例如:
- 任何函数都可以更改包中的全局变量
- 单元测试可能会更复杂,因为依赖于全局变量的函数将不再被隔离
在大多数情况下,我们应该倾向于封装一个变量而不是让它保持全局。
由于这些原因,之前的初始化可能应该封装到一个函数中处理,如下所示:
func createDB() (*sql.DB, error) {
d, err := sql.Open("mysql",
"root:liufutian@tcp(127.0.0.1:3306)/test")
if err != nil {
return nil, err
}
err = db.Ping()
if err != nil {
return nil, err
}
return d, nil
}
这样写的话,我们解决了之前讨论的主要缺点:
- 是否处理错误留给调用者
- 可以创建一个集成测试来检查此功能是否有效
- 连接池封装在函数中
但是这样就是不能使用init函数了么?在我们上面的引入mysql驱动的例子中,说明使用init还是有帮助的:
func init() {
sql.Register("mysql", &MySQLDriver{})
}
上面的例子,通过注册提供的驱动名称使数据库驱动程序可用。
golang中的init初始化函数的更多相关文章
- golang中的init函数以及main函数
首先我们看一个例子:init函数: init 函数可在package main中,可在其他package中,可在同一个package中出现多次. main函数 main 函数只能在package ma ...
- pytorch系列 -- 9 pytorch nn.init 中实现的初始化函数 uniform, normal, const, Xavier, He initialization
本文内容:1. Xavier 初始化2. nn.init 中各种初始化函数3. He 初始化 torch.init https://pytorch.org/docs/stable/nn.html#to ...
- golang中包的初始化
1.当一个go源程序被初始化时,首先去初始化所依赖的其他包,然后初始化该go源码文件的全局变量的初始化和执行初始化函数,其中该包所有的全局变量初始化在前,该包的初始化函数int在后.当所有包的初始化函 ...
- golang中,map作为函数参数是如何传递的
当你声明一个map的时候: m := make(map[int]int) 编译器会调用 runtime.makemap: // makemap implements a Go map creation ...
- golang中字符串内置函数整理
字符串内置函数 1. 判断字符串的长度 str := "korea国" fmt.Println("str len=", len(str)) 2. 字符串遍历,同 ...
- 仙人指路,引而不发,Go lang1.18入门精炼教程,由白丁入鸿儒,Golang中New和Make函数的使用背景和区别EP16
Golang只有二十五个系统保留关键字,二十几个系统内置函数,加起来只有五十个左右需要记住的关键字,纵观编程宇宙,无人能出其右.其中还有一些保留关键字属于"锦上添花",什么叫锦上添 ...
- golang中结构体当做函数参数或函数返回值都会被拷贝
1. 结构体做函数的参数或返回值时,都会被重新拷贝一份如果不想拷贝,可以传递结构体指针 package main import "fmt" type Person struct { ...
- MATLAB——神经网络init初始化函数和adapt函数
- GCC中初始化函数是怎样被处理的?
本文译至: http://gcc.gnu.org/onlinedocs/gccint/Initialization.html 如我们所知,在GCC通过给代码追加__attribute__((const ...
随机推荐
- vue原理相关
vue原理三大模块:响应式.vdom和diff.模板编译 vue原理要点: 1.组件化 组件化的历史:在vue之前已经有组件化的概念了,想asp.jsp.php等就有组件化的概念,nodejs也有组件 ...
- JavaDoc文档生成详细操作
JavaDoc练习 JavaDoc是一种将注释生成HTML文档的技术,是用来生成自己API文档的. 参数信息 /* @author 作者名 @version 版本号 @since 知名最早需要使用的j ...
- 完整实现-通过DelayQueue实现延时任务
实现延时任务有很多的方法,网上关于延时任务的实现的文章已经不少了.比如:实现延时任务的10种方法等等.但是这些文章基本上都是将方法大概的列举一下,给出部分示例代码,对于有经验的老程序员可能一看就知道该 ...
- linux下用docker安装redis
docker安装redis方法: 1.用命令来查看可用版本: docker search redis 2.拉取官方的最新版本的镜像:docker pull redis:latest 3.查看镜像:do ...
- 从new File("")到jdk源码
1. 概述 今天在项目中看到下面两行代码,看注释说是获取当前工作路径,之前也没有用过这种用法,比较好奇还能这样用,所以研究了一下源码. //获取当前工作路径 File file = new File( ...
- Taurus.MVC 微服务框架 入门开发教程:项目部署:5、微服务应用程序发布到Docker部署(下)。
系列目录: 本系列分为项目集成.项目部署.架构演进三个方向,后续会根据情况调整文章目录. 开源地址:https://github.com/cyq1162/Taurus.MVC 本系列第一篇:Tauru ...
- 彻底搞懂C#异步编程 async和await的原理
1.前提 熟练掌握Task并行编程. 2.用Task并行解释async和await异步 因为控制台有多线程操作的优化,因此这里选择winform来做示例. 测试代码如下所示: 有三个textbox,一 ...
- xtrabackup增量备份MySQL-5.7操作说明
下载工具 本方法利用xtrabackup二进制包,版本是2.4.26 # 从官网下载二进制包:wget https://downloads.percona.com/downloads/Percona- ...
- GNSS模块使用笔记
目录 目录 GNSS芯片 NMEA0183 协议 指令 GNSS TO MCU MCU TO GNSS GNSS芯片 ATGM336H-5N31(GPS+BDS双模) 原理图 NMEA0183 协议 ...
- 002从零开始入门Entity Framework Core——DbContext生存期、配置和初始化
阅读须知:本文为入门介绍.指引文章,所示代码皆为最简易(或仅为实现功能)的演示示例版本,不一定切实符合个人(企业)实际开发需求. 一.DbContext生存期 DbContext 的生存期从创建实例时 ...