2017-02-01 7 views
0

Я немного смущен об использовании sync.Mutex в Go. Я понимаю основную концепцию (вызов Lock() предотвратит выполнение другими goroutines кода между ним и Unlock()), но я не уверен, должен ли мне нужен здесь. Я видел довольно мало ответов на C++ для этого, но в каждом примере все они, похоже, изменяют и получают доступ к переменной напрямую.Нужен ли мне мьютекс, если я возвращаю копию переменной, а не указатель?

Это мой код из пакета с именем configuration, который я буду использовать во всем приложении для получения (удивительно) информации о конфигурации и настройках.

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 

} 

Как вы можете видеть, я использовал мьютекс в двух местах.

  • Current(). Нужно ли мне это здесь, если функция не возвращает указатель, а копию переменной programConfig? Единственный способ изменения базовой переменной пакета - через функцию Load().
  • В функции Load(). Это может быть вызвано в любой момент любым горутином, хотя это редко будет.

Учитывая, что я их правильно использовать и почему мне нужен при чтении копию данных (если это так)?

+0

Вы отправили много информации. Это мило. Но трудно понять на месте. Конкретизируйте свою проблему и добавьте изолированный пример в качестве ссылки на игровую площадку golang. – Altenrion

+0

Также я не вижу, где вы используете его одновременно. Пока не будет 'go', вам вообще не нужны мьютексы. Я понимаю, что где-то он использовался одновременно, но это должно быть показано. – I159

+0

@Altenrion сделаю. @ i159 goroutines находятся в основном пакете, это всего лишь конфигурационный пакет, но оба метода «Current()» и «Load()» теоретически могут быть вызваны в любой момент из любого из них. – Leylandski

ответ

5

Когда вы читаете данные, которые могут быть записаны одновременно, вам нужен мьютекс. В противном случае может случиться так, что вы прочтете, пока он написан, и получите половину старых данных и половину новых данных.

Так что ваш пример, кажется, в порядке. Поскольку вы, вероятно, часто читаете конфигурацию, но редко ее используете, использование вами RWLock имеет смысл. Это означает, что несколько потоков могут считываться одновременно, пока они не написаны.

Что в вашем коде выглядит опасно:

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

Поскольку programConfig содержит Mutex вы делаете Lock и Unlock на разных экземплярах, которые приведут к тупикам. Вы должны добавить Mutex к «родительскому» экземпляру экземпляра. В этом случае возможно пакет.

+0

Спасибо, это очень полезный ответ, и вы на самом деле читаете полный вопрос, а не прыгаете на полупериодические выводы, снимая его, а затем вниз, не понимая его, как это часто бывает на этом сайте. – Leylandski

+1

Отличный улов! Мютекст не должен быть частью заменяемых данных. – dolmen