2017-02-06 86 views
0

我一直在拉我的头发,为什么这个代码抛出的错误:参数太多返回错误

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 
    }) 
} 

正如你所看到的,配对完全正确的。每个返回码都会返回&ConfigMaperr,并且函数签名会与其匹配。我错过了什么?

回答

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())。

问题:这里可能有数据竞赛吗?

如果Config()从多个够程叫,至少一个会写的全局变量cmcmErr,一切都会阅读。所以问这个问题是正确的。

但答案是否定的,上面的代码是安全的。全局变量cmcmErr只写入一次,而且这些变量在读取之前就会发生。因为once.Do()阻塞,直到匿名函数返回。如果Config()(和once.Do())在多个例程中同时被调用,则所有操作都将阻塞,直到匿名函数完成(仅一次),并且仅在第一次写入后才能读取变量。而且由于匿名函数将不再运行,所以不会再发生写操作。

1

你叫return nil, err和类似的在你的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 
}