我一直在拉我的头发,为什么这段代码会抛出错误:
max-height
如您所见,配对完全正确。每个返回码返回package util
import (
"path/filepath"
"sync"
"github.com/go-ini/ini"
)
// ConfigMap is map for config values
type ConfigMap struct {
LogPath string
PublicDir string
SessionName string
Debug bool
DBUsersHost string
DBUsersName string
DBUsersUsername string
DBUsersPassword string
}
var once sync.Once
// Config loads and return config object
func Config() (*ConfigMap, error) {
once.Do(func() {
// Find the location of the app.conf file
configFilePath, err := filepath.Abs("../build/app.conf")
if err != nil {
return nil, err
}
// Load app.conf
cfg, err := ini.Load(configFilePath)
if err != nil {
return nil, err
}
// Get app mode
mode, err := AppMode()
if err != nil {
return nil, err
}
c := &ConfigMap{}
err = cfg.Section(mode).MapTo(c)
if err != nil {
return nil, err
}
return c, err
})
}
和&ConfigMap
,函数签名与之匹配。我错过了什么?
答案 0 :(得分:3)
您将匿名函数值传递给once.Do()
(Once.Do()
),return
语句位于其中。这意味着那些return
语句想要从匿名函数返回,但它没有任何返回值:
func Config() (*ConfigMap, error) {
once.Do(func() {
// You can't return any values here, only this works:
return
})
// And you do need to return something here:
return &ConfigMap{}, nil
}
您可能要做的是创建与Config()
的返回值匹配的全局变量,并且匿名函数应该将值存储在其中。在Config()
中,您可以返回这些全局变量的值。
var cm *ConfigMap
var cmErr error
func Config() (*ConfigMap, error) {
once.Do(func() {
// load config, and store, e.g.
cm, cmErr = &ConfigMap{}, nil
})
return cm, cmErr
}
我们真的需要全局变量吗?由于Config()
返回的值是由传递给once.Do()
的匿名函数产生的,保证只运行一次,是的,你需要将它们存储在某个地方以便能够多次返回它们,即使是匿名函数不再被调用/运行(在后续调用Config()
时)。
问题:这里可能有数据竞争吗?
如果从多个goroutine调用Config()
,则至少有一个将编写全局变量cm
和cmErr
,并且所有变量都将读取它们。所以提出这个问题是合法的。
但答案是否定的,上面的代码是安全的。全局变量cm
和cmErr
只写一次,这在它们被读取之前发生。因为once.Do()
会阻塞,直到匿名函数返回。如果从多个goroutine同时调用Config()
(因而once.Do()
),则所有将阻塞直到匿名函数完成(仅一次),并且只有在第一次写入后才能读取变量。由于匿名函数不再运行,因此不会再发生写入。
答案 1 :(得分:1)
您在return nil, err
内的嵌套func()
中呼叫once.Do
和类似内容。相反,您不会从实际功能返回。
相反,您可以像这样构建代码:
func newConfig() (*Config, error) {
configFilePath, err := filepath.Abs("../build/app.conf")
if err != nil {
return nil, err
}
// Load app.conf
cfg, err := ini.Load(configFilePath)
if err != nil {
return nil, err
}
// Get app mode
mode, err := AppMode()
if err != nil {
return nil, err
}
c := &ConfigMap{}
err = cfg.Section(mode).MapTo(c)
if err != nil {
return nil, err
}
return c, err
}
// Cached config and any error.
var (
cachedConfig *Config
cachedConfigErr error
)
func Config() (*Config, error) {
once.Do(func() {
cachedConfig, cachedConfigErr = newConfig()
})
return cachedConfig, cachedConfigErr
}