Do I need a mutex if I am returning a copy of the variable rather than a pointer?

huangapple go评论69阅读模式
英文:

Do I need a mutex if I am returning a copy of the variable rather than a pointer?

问题

我对在Go语言中使用sync.Mutex有些困惑。我理解基本概念(调用Lock()将阻止其他goroutine执行其和Unlock()之间的代码),但我不确定在这里是否_需要_它。我看到了一些关于C++的答案,但在每个示例中,它们似乎都直接修改和访问变量。

这是我从一个名为configuration的包中的代码,我将在整个应用程序中使用它来获取(出乎意料的)配置和设置信息。

package config

import (
	"encoding/json"
	"fmt"
	"os"
	"sync"

	log "github.com/sirupsen/logrus"
)

/*
ConfigurationError是实现错误接口的描述处理此包时发生的错误。
*/
type ConfigurationError string

/*
Error打印此ConfigurationError的错误消息。它还实现了错误接口。
*/
func (ce ConfigurationError) Error() string {
	return fmt.Sprintf("Configuration error: %s", string(ce))
}

/*
configuration是一个保存设置程序所需所有信息的数据结构。它是未导出的,以防止其他包创建更多实例。需要设置信息的其他包应调用Current()来访问未导出的programConfig包变量的副本。
*/
type configuration struct {
	sync.RWMutex
	LogLevel               log.Level `json:"logLevel,omitempty"`     //Logging
	LogLocation            string    `json:"logLocation,omitempty"`  //-
	HttpPort               int       `json:"port,omitempty"`         //Web
	LoginUri               string    `json:"loginUri"`               //-
	WebBaseUri             string    `json:"webBaseUri"`             //-
	StaticBaseUri          string    `json:"staticBaseUri"`          //-
	ApiBaseUri             string    `json:"apiBaseUri"`             //-
	StaticContentLocalPath string    `json:"staticContentLocalPath"` //-
	MaxSimultaneousReports int       `json:"maxSimultaneousReports"` //Reporting
}

var programConfig configuration

/*
Current返回当前加载的程序配置的副本。
*/
func Current() configuration {
	programConfig.RLock()
	defer programConfig.RUnlock()
	return programConfig
}

/*
Load尝试加载包含Configuration结构的表示的JSON设置文件。然后,它将将包级别的config结构的值设置为加载的值。某些设置更改将需要重新启动服务器应用程序。

filepath-包含前导斜杠或驱动器名称(取决于操作系统)的设置文件的完整路径。
*/
func Load(filepath string) error {

	//打开文件进行读取。
	settingsFile, err := os.Open(filepath)
	if err != nil {
		return ConfigurationError(err.Error())
	}
	defer settingsFile.Close()

	//将JSON解码为包级别变量。
	decoder := json.NewDecoder(settingsFile)
	newSettings := configuration{}
	err = decoder.Decode(&newSettings)
	if err != nil {
		return ConfigurationError(err.Error())
	}

	programConfig.Lock() //我不确定这是否是在这种情况下正确使用互斥锁,所以请检查一下。
	programConfig = newSettings
	programConfig.Unlock()
	return nil

}

如你所见,我在两个地方使用了互斥锁。

  • Current()函数中。如果该函数不返回指针而是返回programConfig变量的副本,我是否需要在这里使用互斥锁?底层的包变量只能通过Load()函数进行修改。
  • Load()函数中。这个函数可以被任何goroutine随时调用,尽管很少会这样。

鉴于这一点,我是否正确地使用了它们,并且在读取数据的副本时为什么需要一个互斥锁(如果需要的话)?

英文:

I'm a little confused about the use of sync.Mutex in Go. I understand the basic concept (calling Lock() will prevent other goroutines from executing the code between it and Unlock()), but I'm not sure if I need it here. I've seen a fair few C++ answers for this but in each example they all seem to be modifying and accessing a variable directly.

This is my code from a package called configuration, which I will use throughout the application to get (surprisingly) configuration and settings information.

package config
import (
"encoding/json"
"fmt"
"os"
"sync"
log "github.com/sirupsen/logrus"
)
/*
ConfigurationError is an implementation of the error interface describing errors that occurred while dealing with this
package.
*/
type ConfigurationError string
/*
Error prints the error message for this ConfigurationError. It also implements the error interface.
*/
func (ce ConfigurationError) Error() string {
return fmt.Sprintf("Configuration error: %s", string(ce))
}
/*
configuration is a data struct that holds all of the required information for setting up the program. It is unexported
to prevent other packages creating more instances. Other packages that need settings information should call Current()
to access a copy of the unexported programConfig package variable.
*/
type configuration struct {
sync.RWMutex
LogLevel               log.Level `json:"logLevel,omitempty"`     //Logging
LogLocation            string    `json:"logLocation,omitempty"`  //-
HttpPort               int       `json:"port,omitempty"`         //Web
LoginUri               string    `json:"loginUri"`               //-
WebBaseUri             string    `json:"webBaseUri"`             //-
StaticBaseUri          string    `json:"staticBaseUri"`          //-
ApiBaseUri             string    `json:"apiBaseUri"`             //-
StaticContentLocalPath string    `json:"staticContentLocalPath"` //-
MaxSimultaneousReports int       `json:"maxSimultaneousReports"` //Reporting
}
var programConfig configuration
/*
Current returns a copy of the currently loaded program configuration.
*/
func Current() configuration {
programConfig.RLock()
defer programConfig.RUnlock()
return programConfig
}
/*
Load attempts to load a JSON settings file containing a representation of the Configuration struct. It will then set
the value of the package-level config struct to the loaded values. Some settings changes will require a restart of the
server application.
filepath - the full path of the settings file including a leading slash or drive name (depending on the OS).
*/
func Load(filepath string) error {
//Open the file for reading.
settingsFile, err := os.Open(filepath)
if err != nil {
return ConfigurationError(err.Error())
}
defer settingsFile.Close()
//Decode JSON into package level var.
decoder := json.NewDecoder(settingsFile)
newSettings := configuration{}
err = decoder.Decode(&newSettings)
if err != nil {
return ConfigurationError(err.Error())
}
programConfig.Lock() //I'm not 100% sure this is the correct use of a mutex for this situation, so check up on that.
programConfig = newSettings
programConfig.Unlock()
return nil
}

As you can see, I've used mutex in two places.

  • In Current(). Do I need this here if the function is not returning a pointer but a copy of the programConfig variable? The only way the underlying package variable will be modified is through the Load() function.
  • In the Load() function. This can be called at any time by any goroutine, although it rarely will be.

Given that, am I using them correctly and why do I need one when reading a copy of the data (if so)?

答案1

得分: 8

当你读取可以同时写入的数据时,你需要使用互斥锁(mutex)。否则,可能会发生在写入数据时读取数据,导致读取到一半旧数据和一半新数据的情况。

所以你的示例看起来是没问题的。因为你可能经常读取配置,但很少写入,所以使用RWLock是有意义的。这意味着多个线程可以同时读取,只要没有写入。

你代码中看起来危险的地方是:

programConfig.Lock()
programConfig = newSettings
programConfig.Unlock()

因为programConfig包含了Mutex,你在不同的实例上进行了LockUnlock操作,这将导致死锁。你应该将Mutex添加到实例的“父级”,在这种情况下可能是包。

英文:

When you read data which can be written at the same time you need a mutex. Otherwise it might happen that you read while it's written and get half of the old data and half of the new data.

So your example seems to be just fine. Because you are probably reading the config very often but writing it rarely your usage of a RWLock makes sense. This means that multiple threads can read at the same time as long as it's not written.

What in your code looks dangerous is:

programConfig.Lock()
programConfig = newSettings
programConfig.Unlock()

Because programConfig contains the Mutex you are doing the Lock and Unlock on different instances which will lead to deadlocks. You should add the Mutex to the "parent" of the instance. In this case probably the package.

huangapple
  • 本文由 发表于 2017年2月1日 17:46:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/41976512.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定