2017-11-12 83 views
8
package main 

import "time" 

func main() { 
    i := 1 
    go func() { 
     for { 
      i++ 
     } 
    }() 
    <-time.After(1 * time.Second) 
    println(i) 
} 

Dane wyjściowe są zawsze 1.Czy to dlatego, że kompilator go zoptymalizował kod?

Jednak to absolutnie, że 1s wystarczy, aby pętla for przeszła wiele razy.

Myślę, że i w zamknięciu jest i w funkcji main.

Zobacz poniższy kod.

package main 

import "time" 

func main() { 
    i := 1 
    go func() { 
     for { 
      i++ 
      println("+1") 
     } 
    }() 
    <-time.After(1 * time.Second) 
    println(i) 
} 

Po wielu liniach "+1" wynik jest dokładnie taki, jak oczekiwano.

+0

Program nie drukuje wartość oczekiwaną, ponieważ nie jest to rasa dane o zmiennej 'I'. –

Odpowiedz

11

The Go Memory Model

Wersja z dnia 31 maja 2014 r

Wprowadzenie

modelu pamięci Go określa warunki, na których czyta zmiennej w jednym goroutine można zagwarantować przestrzeganie wartości wytwarzane przez zapis do tej samej zmiennej w innej goroutine.

Porady

programy, które modyfikują dane są jednocześnie dostępne dla wielu goroutines musi serializacji takiego dostępu.

Aby przekształcić do postaci szeregowej dostęp, chroń dane za pomocą operacji na kanale lub innych prymitywów synchronizacji , takich jak te w pakietach synchronizacji i synchronizacji/atomowej .

Jeśli musisz przeczytać resztę tego dokumentu, aby zrozumieć zachowanie swojego programu, jesteś zbyt sprytny.

Nie bądź sprytny.

synchronizacji

var a string 

func hello() { 
    go func() { a = "hello" }() 
    print(a) 
} 

przypisanie do nie następuje po każdym przypadku synchronizacji tak nie gwarantuje się obserwować dowolną drugiej goroutine. W rzeczywistości, agresywny kompilator może usunąć całą instrukcję go.


Przyporządkowanie do i poprzez przyrost i++ (i = i + 1) nie jest po każdym przypadku synchronizacji, tak że nie jest zagwarantowane obserwowano przez inne goroutine. W rzeczywistości agresywny kompilator może usunąć całą instrukcję i++.

Przykładowo

package main 

import "time" 

func main() { 
    i := 1 
    go func() { 
     for { 
      i++ 
     } 
    }() 
    <-time.After(1 * time.Millisecond) 
    println(i) 
} 

wyjściowa:

1 

goroutine redukuje się do:

"".main.func1 STEXT nosplit size=2 args=0x8 locals=0x0 
    0x0000 00000 (elide.go:7) TEXT "".main.func1(SB), NOSPLIT, $0-8 
    0x0000 00000 (elide.go:7) FUNCDATA $0, gclocals·2a5305abe05176240e61b8620e19a815(SB) 
    0x0000 00000 (elide.go:7) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 
    0x0000 00000 (elide.go:9) JMP 0 

kompilator,

for { 
    i++ 
} 

może zostać zaimplementowany poprzez zwiększenie rejestru na zawsze, w zasadzie pętli no-op for.

for { } 

Po włożeniu print oświadczenie,

package main 

import "time" 

func main() { 
    i := 1 
    go func() { 
     for { 
      i++ 
      println("+1") 
     } 
    }() 
    <-time.After(1 * time.Millisecond) 
    println(i) 
} 

wyjściowa:

+1 
+1 
<<SNIP>> 
+1 
+1 
432 

goroutine rozszerza się,

"".main.func1 STEXT size=81 args=0x8 locals=0x18 
    0x0000 00000 (elide.go:7) TEXT "".main.func1(SB), $24-8 
    0x0000 00000 (elide.go:7) MOVQ (TLS), CX 
    0x0009 00009 (elide.go:7) CMPQ SP, 16(CX) 
    0x000d 00013 (elide.go:7) JLS 74 
    0x000f 00015 (elide.go:7) SUBQ $24, SP 
    0x0013 00019 (elide.go:7) MOVQ BP, 16(SP) 
    0x0018 00024 (elide.go:7) LEAQ 16(SP), BP 
    0x001d 00029 (elide.go:7) FUNCDATA $0, gclocals·a36216b97439c93dafebe03e7f0808b5(SB) 
    0x001d 00029 (elide.go:7) FUNCDATA $1, gclocals·33cdeccccebe80329f1fdbee7f5874cb(SB) 
    0x001d 00029 (elide.go:8) MOVQ "".&i+32(SP), AX 
    0x0022 00034 (elide.go:9) INCQ (AX) 
    0x0025 00037 (elide.go:10) PCDATA $0, $0 
    0x0025 00037 (elide.go:10) CALL runtime.printlock(SB) 
    0x002a 00042 (elide.go:10) LEAQ go.string."+1\n"(SB), AX 
    0x0031 00049 (elide.go:10) MOVQ AX, (SP) 
    0x0035 00053 (elide.go:10) MOVQ $3, 8(SP) 
    0x003e 00062 (elide.go:10) PCDATA $0, $0 
    0x003e 00062 (elide.go:10) CALL runtime.printstring(SB) 
    0x0043 00067 (elide.go:10) PCDATA $0, $0 
    0x0043 00067 (elide.go:10) CALL runtime.printunlock(SB) 
    0x0048 00072 (elide.go:9) JMP 29 
    0x004a 00074 (elide.go:9) NOP 
    0x004a 00074 (elide.go:7) PCDATA $0, $-1 
    0x004a 00074 (elide.go:7) CALL runtime.morestack_noctxt(SB) 
    0x004f 00079 (elide.go:7) JMP 0 

Zwiększona złożoność Goro utine oznacza, że ​​kompilator nie rozważa już dedykowania rejestru do wartości i. Wartość in-memory wynosząca i jest zwiększana, co powoduje, że aktualizacje są widoczne w wyścigu danych, do goryntiny main.

================== 
WARNING: DATA RACE 

Read at 0x00c420094000 by 
main goroutine: 
    main.main() 
     /home/peter/gopath/src/lucky.go:14 +0xac 

Previous write at 0x00c420094000 by 
goroutine 5: 
    main.main.func1() 
     /home/peter/gopath/src/lucky.go:9 +0x4e 

Goroutine 5 (running) created at: 
    main.main() 
     /home/peter/gopath/src/lucky.go:7 +0x7a 
================== 

Dla oczekiwanego wyniku, dodać trochę synchronizację,

package main 

import (
    "sync" 
    "time" 
) 

func main() { 
    mx := new(sync.Mutex) 
    i := 1 
    go func() { 
     for { 
      mx.Lock() 
      i++ 
      mx.Unlock() 
     } 
    }() 
    <-time.After(1 * time.Second) 
    mx.Lock() 
    println(i) 
    mx.Unlock() 
} 

wyjściowa:

41807838 
1

równoczesnego dostępu do zmiennej i konieczności być synchronizowane:

synchronizacja lepiej zrobić z kanałami lub urządzeniami pakietu synchronizacji . Współdziel pamięć przez komunikowanie się; nie komunikuj się przez dzielenie pamięci.

ref: https://golang.org/pkg/sync/atomic/


To ciekawe, więc dzielę moje eksperymenty:

0- Twój kod za pomocą time.Sleep(1 * time.Second) (nie zaleca-nie zsynchronizowane):

package main 

import "time" 

func main() { 
    i := 1 
    go func() { 
     for { 
      i++ 
     } 
    }() 
    time.Sleep(1 * time.Second) 
    println(i) 
} 

moc wyjściowa:

1 

1- Stosując i := new(int) (nie zalecane, niezsynchronizowanych)

package main 

import "time" 

func main() { 
    i := new(int) 
    go func() { 
     for { 
      *i++ 
     } 
    }() 
    time.Sleep(1 * time.Second) 
    println(*i) 
} 

wyjściowy (CPU: i7-7700K @ 4.2 GHz)

772252413 

2- synchronizacji przez atomic.AddInt64(&i, 1) (Package atomic provides low-level atomic memory primitives useful for implementing synchronization algorithms)

package main 

import (
    "sync/atomic" 
    "time" 
) 

func main() { 
    i := int64(1) 
    go func() { 
     for { 
      atomic.AddInt64(&i, 1) // free running counter 
     } 
    }() 
    time.Sleep(1 * time.Second) 
    println(atomic.LoadInt64(&i)) // sampling the counter 
} 

Wydajność:

233008800 

3- synchronizacji przez chan int:

package main 

import "time" 

func main() { 
    ch := make(chan int) 
    go func() { 
     timeout := time.NewTimer(1 * time.Second) 
     defer timeout.Stop() 
     i := 1 
     for { 
      select { 
      case <-timeout.C: 
       ch <- i 
       return 
      default: 
       i++ 
      } 
     } 
    }() 
    //time.Sleep(1 * time.Second) 
    println(<-ch) 
} 

Wydajność:

272702341 

4- synchronizacji przez sync.WaitGroup:

package main 

import (
    "fmt" 
    "sync" 
    "time" 
) 

func main() { 
    var done sync.WaitGroup 
    done.Add(1) 
    i := 1 
    go func() { 
     defer done.Done() 
     timeout := time.NewTimer(1 * time.Second) 
     defer timeout.Stop() 
     for { 
      select { 
      case <-timeout.C: 
       return 
      default: 
       i++ 
      } 
     } 
    }() 
    done.Wait() 
    fmt.Println(i) 
} 

Wydajność:

261459418 

5- synchronizacji przez zamknąć kanał:

package main 

import (
    "fmt" 
    "time" 
) 

func main() { 
    quit := make(chan struct{}) 
    i := 1 
    go func() { 
     for { 
      i++ 
      select { 
      case <-quit: 
       return 
      default: 
      } 
     } 
    }() 
    time.Sleep(1 * time.Second) 
    quit <- struct{}{} 
    fmt.Println(i) 
} 

Wydajność:

277366276 

6- synchronizacji przez sync.RWMutex:

package main 

import (
    "sync" 
    "time" 
) 

func main() { 
    var i rwm 
    go func() { 
     for { 
      i.inc() // free running counter 
     } 
    }() 
    time.Sleep(1 * time.Second) 
    println(i.read()) // sampling the counter 
} 

type rwm struct { 
    sync.RWMutex 
    i int 
} 

func (l *rwm) inc() { 
    l.Lock() 
    defer l.Unlock() 
    l.i++ 
} 
func (l *rwm) read() int { 
    l.RLock() 
    defer l.RUnlock() 
    return l.i 
} 

Wydajność:

24271318 

Pokrewne tematy:

The Go Memory Model
There is no equivalent to volatile and register in Go
Does Go support volatile/non-volatile variables?

+0

Podstawową zaletą programu jest poprawność. Nie mówisz, które przykłady są nieprawidłowe. Na przykład, przynajmniej przykłady 0, 1 i 2. – peterSO

+0

Drogi @ PeterSO: Myślę, że przykład 2 nie ma problemu, czy widzisz jakieś? (używając 'atomic.AddInt64 (& i, 1)') –

+0

Model pamięci Go nie zawiera wyraźnej wzmianki o zmiennych atomowych. Niektórzy twierdzą, w najlepszym razie, coś takiego: Implementacja powinna zapewnić, że ostatnia wartość (w porządku modyfikacji) przypisana przez operację atomową stanie się widoczna dla wszystkich innych goroutinów w skończonym czasie. Zobaczysz zaktualizowane wartości 'i' między chwilą a czymś mniej niż nieskończoność. Zasadniczo wartość 'i' jest niezdefiniowana. – peterSO