Go语言中如何让main()等待init()完成MongoDB连接?
问题分析与修复方案
首先来说你的代码为什么失效,主要有两个核心问题:
1. 变量遮蔽(Variable Shadowing)导致全局client未被正确赋值
你在init()函数里写了:
client, err := mongo.Connect(context.TODO(), clientOptions)
这里的:=会在init函数的局部作用域内重新声明一个client变量,完全覆盖了全局定义的var client *mongo.Client。这就导致全局的client始终是nil,如果后续你在main里尝试使用这个全局变量,就会触发空指针引用错误。
2. 无缓冲通道的阻塞问题
你创建的是无缓冲通道var c = make(chan *mongo.Client),而init()函数是在main()执行前就运行的。当init()里执行c <- client时,此时还没有任何goroutine在接收通道的数据,无缓冲通道的发送操作会一直阻塞,直接导致程序无法正常进入main()函数,这也是你代码达不到预期的原因之一。
修复方案
如果你坚持要拆分init()和main()的逻辑,这里有两种靠谱的实现方式:
方式一:修复变量遮蔽,配合缓冲通道
先解决变量遮蔽问题,然后把通道改成带缓冲的,避免init()发送时阻塞:
package main import ( "context" "fmt" "log" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) type Trainer struct { Name string Age int City string } var client *mongo.Client // 创建带1个缓冲的通道,避免发送时阻塞 var c = make(chan *mongo.Client, 1) func init() { clientOptions := options.Client().ApplyURI("mongodb://localhost:27017") // 不要用:=,直接给全局client赋值,单独声明err变量 var err error client, err = mongo.Connect(context.TODO(), clientOptions) if err != nil { log.Fatal(err) } err = client.Ping(context.TODO(), nil) if err != nil { log.Fatal(err) } c <- client } func main() { receivedClient := <-c fmt.Println("Successful connection, client:", receivedClient) // 后续可以用receivedClient或者全局client操作MongoDB }
方式二:用goroutine在init里异步初始化,配合WaitGroup
如果不想用通道,也可以用sync.WaitGroup来确保main()等待初始化完成:
package main import ( "context" "fmt" "log" "sync" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) type Trainer struct { Name string Age int City string } var client *mongo.Client var wg sync.WaitGroup func init() { wg.Add(1) go func() { defer wg.Done() clientOptions := options.Client().ApplyURI("mongodb://localhost:27017") var err error client, err = mongo.Connect(context.TODO(), clientOptions) if err != nil { log.Fatal(err) } err = client.Ping(context.TODO(), nil) if err != nil { log.Fatal(err) } }() } func main() { wg.Wait() // 等待初始化完成后再执行业务逻辑 fmt.Println("Successful connection, client:", client) // 现在可以安全使用client了 }
额外建议
其实在Go里,init()函数本身就是用来做初始化的,如果你只是想拆分初始化和业务逻辑,没必要强行用通道或者goroutine,直接在init()里正确初始化全局client,然后在main()里直接使用就好,这样代码更简洁:
package main import ( "context" "fmt" "log" "go.mongodb.org/mongo-driver/mongo" "go.mongodb.org/mongo-driver/mongo/options" ) type Trainer struct { Name string Age int City string } var client *mongo.Client func init() { clientOptions := options.Client().ApplyURI("mongodb://localhost:27017") var err error client, err = mongo.Connect(context.TODO(), clientOptions) if err != nil { log.Fatal(err) } err = client.Ping(context.TODO(), nil) if err != nil { log.Fatal(err) } } func main() { fmt.Println("Successful connection") // 直接使用全局client操作MongoDB }
内容的提问来源于stack exchange,提问作者Marios Simou
相关产品推荐
相关产品推荐

