2016-03-22 24 views
12

Według blogu GoJak bezpieczne są mapy Golang dla współbieżnych operacji odczytu/zapisu?

Mapy nie są bezpieczne dla jednoczesnego stosowania: to nie jest zdefiniowane, co się dzieje, kiedy czytać i pisać do nich jednocześnie. Jeśli chcesz odczytywać i zapisywać na mapie od równoczesnego wykonywania goroutines, dostęp musi być mediowany przez jakiś mechanizm synchronizacji. (źródło: https://blog.golang.org/go-maps-in-action)

Czy ktoś może to wyjaśnić? Równoczesne operacje odczytu wydają się dopuszczalne w obrębie procedur, ale współbieżne operacje odczytu/zapisu mogą generować warunki wyścigu, jeśli ktoś próbuje odczytać i zapisać do tego samego klucza.

Czy to ostatnie ryzyko może zostać w niektórych przypadkach ograniczone? Na przykład:

  • Funkcja A generuje k i ustawia m [k] = 0. Jest to jedyny czas, jaki A pisze, aby zmapować m. k wiadomo, że nie jest w m.
  • A przechodzi do funkcji B działa jednocześnie
  • A następnie odczytuje m [k]. Jeśli m [k] == 0, czeka, kontynuując tylko wtedy, gdy m [k]! = 0
  • B szuka k na mapie. Jeśli ją znajdzie, B ustawia m [k] na pewną dodatnią liczbę całkowitą. Jeśli nie, czeka, aż k jest wm.

To nie jest kod (oczywiście), ale myślę, że pokazuje kontury przypadku, w którym nawet jeśli A i B będą próbowali uzyskać dostęp do m, nie będzie warunku wyścigu, lub jeśli jest on wygrany ma znaczenie z powodu dodatkowych ograniczeń.

+0

Potrzebujemy odtwarzalnego przykładu: [Jak utworzyć przykład minimalny, pełny i sprawdzalny.] (Http://stackoverflow.com/help/mcve) – peterSO

+1

Uruchom swój kod za pomocą detektora wyścigów. – JimB

+0

Nie bezpieczne. Przejdź 1.6 [dodano wykrywanie przypadków jednoczesnego niewłaściwego wykorzystania map] z najlepszym wysiłkiem (https://golang.org/doc/go1.6#runtime). Środowisko wykonawcze powoduje awarię programu po wykryciu niewłaściwego użycia. –

Odpowiedz

3

Go 1.6 Release Notes

Środowisko wykonawcze dodała detekcję lekki, najlepiej wysiłek jednoczesnego nadużycia mapach. Jak zawsze, jeśli jedna goroutine pisze na mapie, inna goroutine powinna jednocześnie czytać lub pisać mapę. Jeśli w środowisku wykonawczym zostanie wykryty ten warunek, zostanie wydrukowana diagnoza i nastąpi awaria programu. Najlepszym sposobem, aby dowiedzieć się więcej o problemie, jest uruchomienie programu pod wykresem wyścigu, który będzie bardziej niezawodnie identyfikował wyścig i podawał więcej szczegółów.

Mapy są złożonymi, samoregrującymi strukturami danych. Równoczesny dostęp do odczytu i zapisu jest niezdefiniowany.

Bez kodu, nie ma wiele więcej do powiedzenia.

5

Równoczesne czytanie (tylko do odczytu) jest w porządku. Równoczesne zapisywanie i/lub czytanie nie jest w porządku.

Wiele goroutinów może zapisywać i/lub czytać tę samą mapę tylko wtedy, gdy dostęp jest zsynchronizowany, np. poprzez pakiet sync, z kanałami lub innymi środkami.

Twój przykład:

  1. Funkcja A generuje K i M zestawów [k] = 0. Jest to jedyny czas, jaki A pisze, aby zmapować m. k wiadomo, że nie jest w m.
  2. A przechodzi do funkcji B działa jednocześnie
  3. A następnie odczytuje m [k]. Jeśli m [k] == 0, czeka, kontynuując tylko wtedy, gdy m [k]! = 0
  4. B szuka k na mapie. Jeśli ją znajdzie, B ustawia m [k] na pewną dodatnią liczbę całkowitą. Jeśli nie, czeka, aż k jest wm.

Twój przykład ma 2 goroutines: A i B, a A próbuje odczytać m (w kroku 3) i B stara się go napisać (w kroku 4) jednocześnie. Nie ma synchronizacji (nie wspomniałeś o żadnej), więc to tylko nie jest dozwolone/nie jest określone.

Co to znaczy? Nieokreślony oznacza, mimo że B pisze m, A może nigdy nie zaobserwować zmiany. Lub A może zaobserwować zmianę, która się nawet nie zdarzyła. Lub może wystąpić panika. Albo Ziemia może wybuchnąć z powodu niezsynchronizowanego równoczesnego dostępu (chociaż szansa na to drugie jest bardzo mała, może nawet mniejsza niż 1e-40).

Powiązane pytania:

Map with concurrent access

what does not being thread safe means about maps in Go?

What is the danger of neglecting goroutine/thread-safety when using a map in Go?

0

można przechowywać wskaźnik do int w mapie i mają wiele goroutines odczytać int wskazywanego podczas gdy inny wypisze nową wartość do int. Mapa nie jest w tym przypadku aktualizowana.

To nie byłoby idiomatyczne dla Go, a nie to, o co prosiłeś.

Zamiast przekazywać klucz do mapy, można przekazać indeks do tablicy i zaktualizować go za pomocą jednego goroutine, podczas gdy inni odczytują lokalizację.

Ale pewnie zastanawiasz się, dlaczego wartość mapy nie może zostać zaktualizowana o nową wartość, gdy klucz jest już na mapie. Prawdopodobnie nic na temat schematu hashowania mapy nie ulega zmianie - przynajmniej nie biorąc pod uwagę ich obecnej implementacji. Wydaje się, że autorzy Go nie chcą uwzględniać takich szczególnych przypadków. Ogólnie rzecz biorąc, chcą, aby kod był łatwy do odczytania i zrozumienia, a reguła taka, jak uniemożliwienie zapisywania map, kiedy inne goryle mogą być czytane, sprawia, że ​​rzeczy są proste, a teraz w 1.6 mogą nawet zacząć łapać niewłaściwe użycie podczas normalnych runtime - oszczędzając wiele osób wiele godzin debugowanie.

13

Przed Golang 1.6, odczyt współbieżny jest OK, zapis współbieżny nie jest OK, ale zapis i odczyt równoczesny jest OK. Od wersji Golang 1.6 mapy nie można odczytać, gdy jest ona zapisywana. Po Golang 1.6, jednocześnie dojazd powinno być tak:

package main 

import (
    "sync" 
    "time" 
) 

var m = map[string]int{"a": 1} 
var lock = sync.RWMutex{} 

func main() { 
    go Read() 
    time.Sleep(1 * time.Second) 
    go Write() 
    time.Sleep(1 * time.Minute) 
} 

func Read() { 
    for { 
     read() 
    } 
} 

func Write() { 
    for { 
     write() 
    } 
} 

func read() { 
    lock.RLock() 
    defer lock.RUnlock() 
    _ = m["a"] 
} 

func write() { 
    lock.Lock() 
    defer lock.Unlock() 
    m["b"] = 2 
} 

Albo dostaniesz błąd poniżej: enter image description here

Dodano:

Można wykryć wyścig używając go run -race race.go

Zmień funkcję read:

func read() { 
    // lock.RLock() 
    // defer lock.RUnlock() 
    _ = m["a"] 
} 

enter image description here

Innym wyborem:

Jak wiadomo, map został wdrożony przez wiader i sync.RWMutex zablokuje wszystkie wiadra. concurrent-map użyj fnv32 do odstrzelenia klucza, a każde wiadro użyj jednego: sync.RWMutex.

+4

'odroczenie' jest wywoływane przy wychodzeniu z 'func', stąd' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' '' ' – xiaoyi

+2

Równoczesne czytanie i pisanie nigdy nie było ok, nawet przed 1.6 to po prostu nie narzekało. –

+0

To naprawdę fajne rozwiązanie. Ale czy można używać kanałów zamiast muteksu? – newguy

1

Podobnie jak w innych udzielonych tu odpowiedziach, rodzimy typ map nie jest goroutine -safe. Kilka uwag po przeczytaniu aktualnych odpowiedzi:

  1. Nie używaj odroczyć do odblokowania, to ma pewne obciążenie, które ma wpływ na wydajność (patrz this piękny post). Zadzwoń bezpośrednio do odblokowania.
  2. Możesz osiągnąć lepszą wydajność, zmniejszając czas spędzany między zamkami. Na przykład, przesuwając mapę.
  3. Istnieje wspólny pakiet (zbliżający się do 400 gwiazdek na GitHub) używany do rozwiązania tego problemu o nazwie concurrent-maphere, który ma na uwadze wydajność i użyteczność. Możesz go użyć do obsługi problemów z współbieżnością.