英文:
Cannot identify an error in sync.Once usage
问题
我正在进行一门关于Golang的在线课程。以下代码片段被呈现在课程材料中,作为对sync.Once
的误用的示例:
var (
once sync.Once
db *sql.DB
)
func DbOnce() (*sql.DB, error) {
var err error
once.Do(func() {
fmt.Println("Am called")
db, err = sql.Open("mysql", "root:test@tcp(127.0.0.1:3306)/test")
if err != nil {
return
}
err = db.Ping()
})
if err != nil {
return nil, err
}
return db, nil
}
据说上述代码是一个有错误的SQL连接管理器的实现。我们作为学生需要自己找出错误,但我很困惑。即使在并行情况下,代码也可以正常运行。我是这样使用它的:
func main() {
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
go (func() {
db, err := DbOnce()
if err != nil {
panic(err)
}
var v int
r := db.QueryRow("SELECT 1")
err = r.Scan(&v)
fmt.Println(v, err)
wg.Done()
})()
}
wg.Wait()
}
我理解在这里不鼓励作业问题,所以我不是在寻求完整的解决方案,只是希望得到一点提示。错误是否与并发有关(即我需要在特定的并发上下文中运行它)?是否与sql.Open的使用有关?
英文:
I'm doing an online course on Golang. The following piece of code is presented in the course material as an example of misuse of sync.Once
:
var (
once sync.Once
db *sql.DB
)
func DbOnce() (*sql.DB, error) {
var err error
once.Do(func() {
fmt.Println("Am called")
db, err = sql.Open("mysql", "root:test@tcp(127.0.0.1:3306)/test")
if err != nil {
return
}
err = db.Ping()
})
if err != nil {
return nil, err
}
return db, nil
}
Supposedly, the above is a faulty implementation of an SQL connection manager. We, the students, are to find the error ourselves, which I struggle with. The code runs fine even in parallel. This is how I used it:
func main() {
wg := sync.WaitGroup{}
wg.Add(10)
for i := 0; i < 10; i++ {
go (func() {
db, err := DbOnce()
if err != nil {
panic(err)
}
var v int
r := db.QueryRow("SELECT 1")
err = r.Scan(&v)
fmt.Println(v, err)
wg.Done()
})()
}
wg.Wait()
}
I understand that homework questions are discouraged here, so I'm not asking for a complete solution, just a hint would be fine. Is the error related to concurrency (i.e. I need to run it in a specific concurrent context)? Is it usage of sql.Open specifically?
答案1
得分: 3
db
变量的初始化是正确的。问题出在返回的错误上。
如果第一次调用DbOnce()
时打开数据库连接失败,那么错误将被正确返回。但是后续的调用呢?db
的初始化代码将不会再次运行,因此可能会返回nil
的db
,并且由于初始化代码没有运行,err
变量的默认值将被返回,即nil
。总结一下,初始化错误被丢失了,不会再报告了。
一种解决方案是在连接失败时停止应用程序(在第一次调用时)。另一种选择是将初始化错误与db
一起存储在包级别的变量中,并从DbOnce()
返回它(不使用局部变量)。前者的优点是你不必处理从DbOnce()
返回的错误,因为它甚至不需要返回错误(如果有错误,你的应用程序将终止)。
后者的代码可能如下所示:
var (
once sync.Once
db *sql.DB
dbErr error
)
func DbOnce() (*sql.DB, error) {
once.Do(func() {
fmt.Println("Am called")
db, dbErr = sql.Open("mysql", "root:test@tcp(127.0.0.1:3306)/test")
if dbErr != nil {
return
}
dbErr = db.Ping()
})
return db, dbErr
}
英文:
Initialization of the db
variable is OK. The problem is with the returned error.
If you call DbOnce()
for the first time and opening a DB connection fails, that error will be returned properly. But what about subsequent calls? The db
initialization code will not be run again, so nil
db
may be returned, and since the initialization code is not run, the default value of the err
variable is returned, which will be nil
. To sum it up, the initialization error is lost and will not be reported anymore.
One solution is to stop the app if connection fails (at the first call). Another option is to store the initialization error too in a package level variable along with db
, and return that from DbOnce()
(and not use a local variable for that). The former has the advantage that you don't have to handle errors returned from DbOnce()
, as it doesn't even have to return an error (if there's an error, your app will terminate).
The latter could look like this:
var (
once sync.Once
db *sql.DB
dbErr error
)
func DbOnce() (*sql.DB, error) {
once.Do(func() {
fmt.Println("Am called")
db, dbErr = sql.Open("mysql", "root:test@tcp(127.0.0.1:3306)/test")
if dbErr != nil {
return
}
dbErr = db.Ping()
})
return db, dbErr
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论