【golang】init函数的一些理解

起因

吃完饭,跟同事们散步,聊到了项目中公共SDK库中的一些缺陷。由来是项目才启动时,封装公共库的员工,在库中使用init函数进行配置文件的解析。从而使业务团队在编写单元测试时,带来了问题。 突然,我想到一个问题,如果乱用了init函数,而该函数中没有日志输出,导致进程假死,那么有什么手段可以快速定位到?

init函数

举例说明

在github.com/go-sql-driver/mysql/driver.go中:

func init() {
    if driverName != "" {
        sql.Register(driverName, &MySQLDriver{})
    }
}

会对sql.Register进行初始化操作。

怎么使用

// package-init/init-mysql/main.go
package main

import (
    "database/sql"
    "log"

    _ "github.com/go-sql-driver/mysql"
)

func main() {
    db, err := sql.Open("mysql""user:password@/dbname")
    if err != nil {
        panic(err)
    }
    log.Println(db)
    //...
}
  • 在导入声明中,我们必须导入语句
    • 第一个“database/sql”导入标准库包。
    • _“github.com/go-sql-driver/mysql”是一个空白导入,–>调用了init函数
  • 然后在主函数中,我们调用 “database/sql”中的Open函数。它将打开与数据库的连接。

是什么

init函数的目的是:package 初始化。 有以下的特点:

  • 没有参数
  • 没有返回值—>无法进行错误的返回。

以上例子:我们的主包中没有 init 函数的踪迹…因此这是完全可以理解的,因为:

  • mysql的驱动程序中的 init 函数未导出。
  • 因此不可能从另一个包中调用它。

如果我们不调用init函数,Go如何注册驱动程序呢?

  • 我们的程序将调用包中的 init 函数。
  • 之所以调用它,是因为我们使用空白导入_语句导入了驱动程序。

当您使用空白导入语句导入包时,将运行包初始化函数。 请注意,驱动程序包的作者还可以删除 init 函数,特别是指示用户手动调用 Register 函数。

初始化的规则

package的初始化,按照 go 规范定义的特定顺序进行:

  • 初始化导入的包:
    • 包中变量初始化
    • 运行初始化函数
  • 然后当前package本身初始化
    • 变量已初始化
    • 运行初始化函数Init 函数

按顺序运行。换句话说,init 函数不是同时运行的。

另外:一个包中可以包含多个init函数,执行的顺序是按照文件的排序执行。

回到问题

如果乱用了init函数,而该函数中没有日志输出,导致进程假死,那么有什么手段可以快速定位到?举例, 目录如下:

├── baz
│   └── baz.go
├── dtm-demo
├── foo
│   ├── a.go
│   ├── bar
│   │   └── bar.go
│   ├── foo2.go
│   └── foo.go
└── main.go

每个包中都包含一个init函数,而foo包中每个文件都存在一个init函数, 输出如下:

in baz
in bar
in a
in foo
in foo2
hello foo
Hello Bar

如何定位init函数,有以下几个方式:暴力思路:从main函数入手,对每个main导入的包进行递归查询,查看每个包以及包依赖包有没有init函数。这种方式可以说非常耗时以及慢了。工具的使用:

  1. **go tool nm**可以查看golang程序的符号表。 那么我们可以通过以下命令输出当前二进制文件包含的init函数:
go tool nm ./dtm-demo  |grep ".init.[0-9]"

命令的目的是输出包含 init.<序号>的函数。进而通过排序标准包的影响来定位问题。(一般情况下使用init函数很少,所以可以从自家的代码入手排查)

  1. 使用go-callvis进行程序调用链的查看。

【golang】init函数的一些理解通过交互模式,可以快速查看调用链 。

结论

非必要的情况下,尽量避免使用init函数。init函数不会失败(只有当处理程序为nil时才会发生),init函数可能会导致一些问题:

  • 限制错误管理,无返回值,无显式的调用。
  • 使测试实现变得复杂(例如,必须设置外部依赖项,这在单元测试的范围内可能是不必要的)。


原文始发于微信公众号(小唐云原生):【golang】init函数的一些理解

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/247513.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!