2013-03-02 8 views
12

Właśnie zauważyłem niepokojące zachowanie. Powiedzmy mam samodzielny program składający się z jedynego obiektu:Scala: Równoległe gromadzenie w inicjatorze obiektu powoduje zawieszenie programu

object ParCollectionInInitializerTest { 
    def doSomething { println("Doing something") } 

    for (i <- (1 to 2).par) { 
    println("Inside loop: " + i) 
    doSomething 
    } 

    def main(args: Array[String]) { 
    } 
} 

Program jest całkowicie niewinny, a gdy zakres stosowany w pętli for jest nie równolegle jeden, wykonuje prawidłowo, z następującym wyjścia :

Wewnątrz pętli: 1
Uprawiając
Wewnątrz pętli: 2
Uprawiając

Niestety, podczas korzystania z kolekcji równoległego, program po prostu wisi bez kiedykolwiek wywołanie metody doSomething, więc wyjście jest w następujący sposób:

Wewnątrz pętli: 2
Wewnątrz pętli: 1

A następnie program się zawiesza.
Czy to tylko paskudny błąd? Używam scala-2.10.

+0

powiązane: http: // stackoverflow.com/questions/27549671/how-to-diagnose-lub-detect-deadlocks-in-java-static-initializers – Rich

Odpowiedz

15

Jest to nieodłączny problem, który może wystąpić w programie Scala po zwolnieniu odwołania do obiektu singleton przed ukończeniem budowy. Dzieje się tak z powodu innego wątku próbującego uzyskać dostęp do obiektu, zanim zostanie on w pełni skonstruowany. Nie ma to nic wspólnego z metodą main, a raczej z inicjowaniem obiektu zawierającego metodę main - spróbuj uruchomić to w REPL, wpisując wyrażenie ParCollectionInInitializerTest, a otrzymasz te same wyniki. Nie ma też nic wspólnego z wątkami roboczymi łączenia widmowego będącymi wątkami demona.

Obiekty Singleton są inicjowane leniwie. Każdy obiekt singletonowy można zainicjować tylko jeden raz. Oznacza to, że pierwszy wątek, który uzyskuje dostęp do obiektu (w twoim przypadku główny wątek) musi chwycić blokadę obiektu, a następnie zainicjować go. Każdy kolejny wątek, który nadejdzie później, musi poczekać na główny wątek, aby zainicjować obiekt i ostatecznie zwolnić blokadę. W ten sposób obiekty singleton są implementowane w Scali.

W twoim przypadku wątek pracownika pobierania równoległego próbuje uzyskać dostęp do obiektu singleton, aby wywołać doSomething, ale nie może tego zrobić, dopóki główny wątek nie zakończy inicjowania obiektu - więc czeka. Z drugiej strony wątek główny czeka w konstruktorze, aż operacja równoległa zakończy się, co jest uzależnione od ukończenia wątków roboczych - główny wątek cały czas utrzymuje blokadę inicjalizacji dla singletonu. Dlatego pojawia się impas.

Można spowodować ten problem z kontraktami od 2,10, lub ze zwykłych wątków, jak pokazano poniżej:

def execute(body: =>Unit) { 
    val t = new Thread() { 
    override def run() { 
     body 
    } 
    } 

    t.start() 
    t.join() 
} 

object ParCollection { 

    def doSomething() { println("Doing something") } 

    execute { 
    doSomething() 
    } 

} 

Wklej to do REPL, a następnie napisać:

scala> ParCollection 

i rEPL zawiesza się.

+2

Doskonałe wyjaśnienie, dzięki! –

+1

Równoczesne wykonywanie blokowania i leniwy inicjowanie nie grają ładnie razem. Jest to bardziej ogólny problem w Scali (i Java, jeśli o to chodzi). Zobacz: SIP: http://docs.scala-lang.org/sips/pending/improved-lazy-val-initialization.html – axel22

+0

Nie jest to replika, ale uważam, że jest to pułapka dla programistów. Jestem niezmiernie wdzięczny za wstępną odpowiedź i komentarze. Uważam, że odpowiedź była dla mnie jasna. – matanster