2016-05-05 5 views
3

Во время кодирования я столкнулся с проблемой. Когда я использую метод внутренней структуры в goroutine, я не вижу внутреннего состояния, как в этом коде.Неправильная инициализация структуры?

package main 

import (
    "fmt" 
    "time" 
) 

type Inner struct { 
    Value int 
} 

func (c Inner) Run(value int) { 
    c.Value = value 
    for { 
     fmt.Println(c.Value) 
     time.Sleep(time.Second * 2) 
    } 

} 

type Outer struct { 
    In Inner 
} 

func (c Outer) Run() { 
    go c.In.Run(42) 

    for { 
     time.Sleep(time.Second) 
     fmt.Println(c.In) 
    } 
} 

func main() { 
    o := new(Outer) 
    o.Run() 
} 

Программа печати:

from inner: {42} 
from outer: {0} 
from outer: {0} 
from inner: {42} 
from outer: {0} 
from inner: {42} 
from outer: {0} 
from outer: {0} 

Может быть, это проблема указатель, но я не знаю, как решить.

ответ

3

Самая очевидная ошибка в вашем коде состоит в том, что Inner.Run() имеет значение-приемник, что означает, что он получает копию типа Inner. Когда вы изменяете это, вы изменяете копию, и вызывающий объект не видит никаких изменений в значении Inner.

Так первый изменить его, чтобы иметь указатель-приемник:

func (c *Inner) Run(value int) { 
    // ... 
} 

Если метод имеет указатель-приемник, адрес (указатель) значения метод вызывается на будет передан методу , И внутри метода вы будете изменять значение , указывающее значение, а не указатель. Указатель указывает на то же значение, которое присутствует у вызывающего, поэтому одно и то же значение изменяется (а не копия).

Только это изменение может заставить ваш код работать. Однако вывод вашей программы не является детерминированным, потому что вы изменяете переменную (поле) из одного goroutine, и вы читаете эту переменную из другого goroutine, поэтому вы должны каким-то образом синхронизировать доступ к этому полю.

Один из способов синхронизации доступа используется sync.RWMutex:

type Inner struct { 
    m  *sync.RWMutex 
    Value int 
} 

При создании значения Outer, инициализировать этот мьютекс:

o := new(Outer) 
o.In.m = &sync.RWMutex{} 

Или в одной строке:

o := &Outer{In: Inner{m: &sync.RWMutex{}}} 

И в Inner.Run() блокировка при доступе к е Inner.Value поле:

func (c *Inner) Run(value int) { 
    c.m.Lock() 
    c.Value = value 
    c.m.Unlock() 

    for { 
     c.m.RLock() 
     fmt.Println(c.Value) 
     c.m.RUnlock() 
     time.Sleep(time.Second * 2) 
    } 
} 

И вы также должны использовать блокировку при доступе на поле в Outer.Run():

func (c Outer) Run() { 
    go c.In.Run(42) 

    for { 
     time.Sleep(time.Second) 
     c.In.m.RLock() 
     fmt.Println(c.In) 
     c.In.m.RUnlock() 
    } 
} 

Примечание:

Ваш пример изменяет только Inner.Value один раз, в начало Inner.Run. Таким образом, приведенный выше код делает много ненужных блокировок/разблокировок, которые могут быть удалены, если цикл в Outer.Run() будет ждать до тех пор, пока значение не будет установлено, и после этого оба goroutines могли бы прочитать переменную без блокировки. В общем случае, если переменную можно изменить и в более поздние времена, приведенная выше блокировка/разблокировка требуется при каждом чтении/записи.

+0

Спасибо за вашу решимость! Теперь я понимаю, как это работает. Теперь код работает хорошо. –

3

Самый простой способ решить проблему заключается в использовании приемника указателя в вашей Run функции:

func (c *Inner) Run(value int) { 
    out = make(chan int) 
    c.Value = value 
    for { 
     fmt.Println(c.Value) 
     time.Sleep(time.Second * 2) 
    } 
} 

Но другое решение было бы использовать из канала, к которому вы можете отправить значение Inner STRUCT:

func (c Inner) Run(value int) { 
    out = make(chan int) 
    c.Value = value 
    for { 
     fmt.Println(c.Value) 
     time.Sleep(time.Second * 2) 
     out <- c.Value 
    } 
} 

Затем в отдельном goroutine, чтобы получить обратно отправленное значение:

for{ 
    go func() { 
     c.In.Run(42) 
     <-out 
     fmt.Println(out) 
    }() 
    time.Sleep(time.Second)  
} 

Вот полный код:

package main 

import (
    "fmt" 
    "time" 
) 

type Inner struct { 
    Value int 
} 

var out chan int 

func (c Inner) Run(value int) { 
    out = make(chan int) 
    c.Value = value 
    for { 
     fmt.Println(c.Value) 
     time.Sleep(time.Second * 2) 
     out <- c.Value 
    } 
} 

type Outer struct { 
    In Inner 
} 

func (c Outer) Run() {  

    for{ 
     go func() { 
      c.In.Run(42) 
      <-out 
      fmt.Println(out) 
     }() 
     time.Sleep(time.Second)  
    } 
} 

func main() { 
    o := new(Outer) 
    o.Run() 
} 

https://play.golang.org/p/Zt_NAsM98_