Programowanie równoległe Część 4 – własne wnioski

Post on 06-Feb-2016

30 views 0 download

description

Programowanie równoległe Część 4 – własne wnioski. Jakub Binkowski. O mnie. Jakub Binkowski Senior .NET Developer @ Rule Financial Microsoft Most Valuable Professional (MVP) MCP, MCTS, MCPD Lider Łódzkiej Grupy Profesjonalistów IT & .NET jakub@binkowski.com.pl. - PowerPoint PPT Presentation

Transcript of Programowanie równoległe Część 4 – własne wnioski

Programowanie równoległeCzęść 4 – własne wnioski

Jakub Binkowski

O mnie Jakub Binkowski Senior .NET Developer @ Rule Financial

Microsoft Most Valuable Professional (MVP) MCP, MCTS, MCPD

Lider Łódzkiej Grupy Profesjonalistów IT & .NET

jakub@binkowski.com.pl

Cykl „Programowanie równoległe w .NET” Część I

Wielowątkowość w .NET 4.0 Część II

Interakcja (synchronizacja) między wątkami, struktury danych i algorytmy równoległe

Część IIIOperacje asynchroniczne

Część IVDobre praktyki, najczęstsze błędy, testowanie

Agenda Błędy Dobre praktyki Ogólne rady Testowanie

Niepewne założenia

Niepewne założenia

var list = new List<int> {1, 3, 4, 5};

Co zwróci instrukcja: list.Contains(5);

wywołana jednocześnie z: list.Insert(index: 1, item: 2);

?

Niepewne założenia Czym jest List<T>?

length = 4

items = 1 3 4 50 1 2 3 4

Niepewne założenia Jak działa list.Contains(5)?

length = 4

items = 1 3 4 50 1 2 3 4

Niepewne założenia Jak działa list.Contains(5)?

length = 4

items = 1 3 4 50 1 2 3 4

FALSE

Niepewne założenia Jak działa list.Contains(5)?

length = 4

items = 1 3 4 50 1 2 3 4

FALSE

Niepewne założenia Jak działa list.Contains(5)?

length = 4

items = 1 3 4 50 1 2 3 4

FALSE

Niepewne założenia Jak działa list.Contains(5)?

length = 4

items = 1 3 4 50 1 2 3 4

TRUE

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 4 50 1 2 3 4

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 4 50 1 2 3 4

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 4 5 50 1 2 3 4

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 4 5 50 1 2 3 4

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 4 4 50 1 2 3 4

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 4 4 50 1 2 3 4

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 3 4 50 1 2 3 4

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 3 3 4 50 1 2 3 4

2

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 4

items = 1 2 3 4 50 1 2 3 4

2

Niepewne założenia Jak działa list.Insert(index: 1, item: 2)?

length = 5

items = 1 2 3 4 50 1 2 3 4

Niepewne założeniaList.Insert(1,2) i List.Contains(5) razemlength = 4

items = 1 3 4 5 50 1 2 3 4

5? FALSE

Niepewne założeniaList.Insert(1,2) i List.Contains(5) razemlength = 4

items = 1 3 4 4 50 1 2 3 4

5? FALSE

Niepewne założeniaList.Insert(1,2) i List.Contains(5) razemlength = 4

items = 1 3 3 4 50 1 2 3 4

5? FALSE

Niepewne założeniaList.Insert(1,2) i List.Contains(5) razemlength = 4

items = 1 2 3 4 50 1 2 3 4

5? FALSE

2

Niepewne założeniaList.Insert(1,2) i List.Contains(5) razemlength = 5

items = 1 2 3 4 50 1 2 3 4

Niepewne założeniaList.Insert(1,2) i List.Contains(5) razemlength = 5

items =

List.Contains(5) = false!

1 2 3 4 50 1 2 3 4

Dobra praktyka Nie zakładaj, że jakieś 2 operacje są

bezpieczne wielowątkowo

Synchronizuj jednoczesny dostęp: lock (Monitor) ReaderWriterLock(Slim) itp.

Używaj kolekcji bezpiecznych wielowątkowo:System.Collections.Concurrent

Instrukcje (nie)atomowe

Problem: Instrukcje (nie)atomowe

jedna linia !=

jedna instukcja

Co się stało? i++ to 3 instrukcje:

LOAD @i, R0 INCREMENT R0 STORE R0, @i

Co się stało?Wątek 1

R0 = 0

i++

Wątek 2

R0 = 0

i++

Pamięć:

i = 5

Co się stało?Wątek 1

R0 = 0

i++

Wątek 2

R0 = 0

i++

Pamięć:

i = 5

Co się stało?Wątek 1

R0 = 0

i++: LOAD @i, R0

Wątek 2

R0 = 0

i++:

LOAD @i, R0

Pamięć:

i = 5

Co się stało?Wątek 1

R0 = 5

i++: LOAD @i, R0

Wątek 2

R0 = 5

i++:

LOAD @i, R0

Pamięć:

i = 5

Co się stało?Wątek 1

R0 = 6

i++: LOAD @i, R0 INC R0

Wątek 2

R0 = 5

i++:

LOAD @i, R0

Pamięć:

i = 5

Co się stało?Wątek 1

R0 = 6

i++: LOAD @i, R0 INC R0 STORE R0, @i

Wątek 2

R0 = 5

i++:

LOAD @i, R0

Pamięć:

i = 6

Co się stało?Wątek 1

R0 = 6

i++: LOAD @i, R0 INC R0 STORE R0, @i

Wątek 2

R0 = 6

i++:

LOAD @i, R0 INC R0

Pamięć:

i = 6

Co się stało?Wątek 1

R0 = 6

i++: LOAD @i, R0 INC R0 STORE R0, @i

Wątek 2

R0 = 6

i++:

LOAD @i, R0 INC R0 STORE R0, @i

Pamięć:

i = 6

Dobra praktyka Nie zakładaj, że „jedna linia = jedna

instrukcja”

Synchronizuj dostęp (lock, itp.)

Używaj gwarantowanych operacji atomowych, np.:Interlocked.Increment(ref _counter);

Zdarzenia (events)

Zdarzenia (events) - przypomnienie Deklaracja:public event EventHandler SomethingHappened;

Wywołanie:if (SomethingHappened != null)

SomethingHappened(this, EventArgs.Empty);

Subskrypcja:SomethingHappened += HandleSomethingHappened;

SomethingHappened -= HandleSomethingHappened;

Problem: Zdarzenia a wielowątkowość

Czy zdarzenia są bezpiecznewielowątkowo?

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

SomethingHappened = null

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

SomethingHappened = HandleSomethingHappen

ed

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

if (SomethingHappened != null)

SomethingHappened = HandleSomethingHappen

ed

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

if (SomethingHappened != null)

SomethingHappened = HandleSomethingHappen

ed

true

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

SomethingHappened -=

HandleSomethingHappened;

if (SomethingHappened != null)

SomethingHappened = HandleSomethingHappen

ed

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

SomethingHappened -=

HandleSomethingHappened;

if (SomethingHappened != null)

SomethingHappened = null

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

SomethingHappened -=

HandleSomethingHappened;

if (SomethingHappened != null)

SomethingHappened(this,

EventArgs.Empty);

SomethingHappened = null

Co się stało?

Wątek 1 Wątek 2

SomethingHappened +=

HandleSomethingHappened;

SomethingHappened -=

HandleSomethingHappened;

if (SomethingHappened != null)

SomethingHappened(this,

EventArgs.Empty);

SomethingHappened = null

NullReferenceException

Przykład

Zdarzenia bezpieczne

wielowątkowo

Dobra praktyka Wzorzec użycia eventów:

public event EventHandler SomethingHappened;

protected void OnSomethingHappened(EventArgs args)

{

var handler = SomethingHappened;

if (handler != null)

{

handler(this, args);

}

}

Dobra praktyka Wzorzec użycia eventów:

public event EventHandler SomethingHappened;

protected void OnSomethingHappened(EventArgs args)

{

var handler = SomethingHappened;

if (handler != null)

{

handler(this, args);

}

}

Dobra praktyka Wzorzec użycia eventów:

public event EventHandler SomethingHappened;

protected void OnSomethingHappened(EventArgs args)

{

var handler = SomethingHappened;

if (handler != null)

{

handler(this, args);

}

}

Dobra praktyka Wzorzec użycia eventów:

public event EventHandler SomethingHappened;

protected void OnSomethingHappened(EventArgs args)

{

var handler = SomethingHappened;

if (handler != null)

{

handler(this, args);

}

}

Dobra praktyka Wzorzec użycia eventów:

public event EventHandler SomethingHappened;

protected void OnSomethingHappened(EventArgs args)

{

var handler = SomethingHappened;

if (handler != null)

{

handler(this, args);

}

}

Snippet„invok

e”

Dobra praktyka Wzorzec użycia eventów:

public event EventHandler SomethingHappened;

protected void OnSomethingHappened(EventArgs args)

{

var handler = SomethingHappened;

if (handler != null)

{

handler(this, args);

}

}

Zdarzenia – pytanie Czy subskrypcja jest bezpieczna

wielowątkowo?SomethingHappened += HandleSomethingHappened;

A co jeśli dodajemy drugą subskrypcję? Czy łączenie dwóch delegatów jest bezpieczne?

SomethingHappened += HandleSomethingHappened;SomethingHappened += HandleSomethingHappened2;

Events internals – C# 1.0-3.5private EventHandler _somethingHappened;

public event EventHandler SomethingHappened

{

add

{

lock (this)

{

_somethingHappened = somethingHappened + value;

}

}

remove {/*kod analogiczny*/}

}

Events internals – C# 4.0private EventHandler _somethingHappened;

public event EventHandler SomethingHappened

{

add

{

/*bezpieczny wielowątkowo kod wolny od lock, który dodaje delegat to _somethingHappened*/

}

remove {/*kod analogiczny*/}

}

Zdarzenia – odpowiedzi Czy subskrypcja jest bezpieczna

wielowątkowo? Tak!

Generowany kod jest wielowątkowo bezpieczny.

Czy łączenie dwóch delegatów jest bezpieczne?

Tak! Złączenie 2 delegatów powoduje powstanie trzeciego. Delegaty są stałe, a więc z definicji wielowątkowo bezpieczne.

Lock escape

Przykład

Ucieczkaz lock’a

Problempublic class SafeList<T>: IEnumerable<T>

{

private List<T> _list = new List<T>();

/*...*/

public IEnumerator<T> GetEnumerator()

{

lock (_list)

{

return _list.GetEnumerator();

}

}

}

Problempublic class SafeList<T>: IEnumerable<T>

{

private List<T> _list = new List<T>();

/*...*/

public IEnumerator<T> GetEnumerator()

{

lock (_list)

{

return _list.GetEnumerator();

}

}

}

Zwrócony enumerator wcale nie jest zabezpieczony!

Dobre praktyki Unikaj zwracania enumeratora kolekcji

„chronionych” przez lock („lock escape”)

GetEnumerator() może: iterować po kopii kolekcji zwracać wielowątkowo bezpieczny iterator

Jak to wygląda w .NET? Enumerator po migawce (snapshot) kolekcji:

ConcurrentQueue ConcurrentStack ConcurrentBag

Enumerator dynamiczny: ConcurrentDictionary

Przykład Poprawna implementacja GetEnumerable():

enumeracja po kopii wielowątkowo bezpieczny enumerator

Lock leak

Przykład

Wyciek lock’a

Lock leak – zły kodprivate object _sync = new object(); public event EventHandler SomethingHappened;

private void DoSomething(){ lock (_sync) { //do something... var handler = SomethingHappened; if (handler != null) { handler(this, EventArgs.Empty); } }}

Lock leak – zły kodprivate object _sync = new object(); public event EventHandler SomethingHappened;

private void DoSomething(){ lock (_sync) { //do something... var handler = SomethingHappened; if (handler != null) { handler(this, EventArgs.Empty); } }}

Wykonujemy nieznany zewnętrzny kod wewnątrz „lock”

Przykład

Poprawiony kod

private object _sync = new object(); public event EventHandler SomethingHappened;

private void DoSomething(){ lock (_sync) { //do something... }

var handler = SomethingHappened; if (handler != null) { handler(this, EventArgs.Empty); }}

Lock leak – poprawiony kod

private object _sync = new object(); public event EventHandler SomethingHappened;

private void DoSomething(){ lock (_sync) { //do something... }

var handler = SomethingHappened; if (handler != null) { handler(this, EventArgs.Empty); }}

Lock leak – poprawiony kod

private object _sync = new object(); public event EventHandler SomethingHappened;

private void DoSomething(){ lock (_sync) { //do something... }

var handler = SomethingHappened; if (handler != null) { handler(this, EventArgs.Empty); }}

Lock leak – poprawiony kod

Wykonujemy nieznany zewnętrzny kod poza „lock”

Kod wielowątkowo bezpieczny

Dobre rady wujka Kuby

Kilka ważnych pytań Czy wiesz gdzie w Twojej aplikacji wątki się

rodzą, a gdzie (i jak) umierają?

Czy interakcja między wątkami odbywa się w ściśle określonych miejscach?

Czy dokumentujesz założenia dot. wielowątkowości kodu?

Czy Twój kod jest prosty?

Jak wygląda start wątku w Twojej aplikacji?

Wątek to nie bomba! Dobrze przemyśl, w którym miejscu

wywoływany jest kod asynchroniczny: interakcje użytkownika (wątek UI) Timer, Task / ThreadPool komunikacja (WCF, messaging, itp.)

Wątek to nie bomba! Zastanów się, co dzieje się z wątkiem (kodem)

później.

Czy łapiesz na końcu (początku) ew. wyjątki?

Czy przy wyjściu z aplikacji czekasz na zakończenie utworzonych wątków?

Podejście „let the shit hit the fan” się nie sprawdza!

Interakcje między wątkami Zastanów się, w którym miejscu się wątki

spotykają.

Czy te klasy i metody są odpowiednio zabezpieczone?

Czy te interakcje są przejrzyste?

Dokumentowanie Czy taki wpis jest Ci obcy?/// <remarks>

/// Any public static (Shared in Visual Basic)

/// members of this type are thread safe.

/// Any instance members are not guaranteed

/// to be thread safe.

/// </remarks>

Albo taki?/// <remarks>

/// This method is thread safe.

/// </remarks>

Co warto dokumentować? Czy klasa została zaprojektowana dla

wielowątkowego użycia? Założenia – czego wymaga i co gwarantuje

dany kod Klasy aktywne (wywołujące kod

asynchroniczny):/// <remarks>/// Event is invoked on ThreadPool thread./// </remarks>public event EventHandler MessageReceived;

Bardziej skomplikowane algorytmy Cykl życia klasy a wielowątkowość

Złoty środek Komentarze sprawiają, że przejrzysty kod

jest bardziej zrozumiały

Jeżeli na 1 linię kodu przypada 5 wyjaśnień, to może lepiej napisać inaczej kod?

Prostota Jedynym sposobem oceny poprawności kodu –

analiza

Wielowątkowość sama z siebie jest skomplikowana i trudna. Niech kod wielowątkowy będzie chociaż prosty. Inaczej nikt nie będzie w stanie ocenić jego poprawności!

Brzydki kod + nieprzemyślana wielowątkowość = spaghetti2

Wielowątkowość a testowalność

Testowanie a kod równoległy Test jednostkowy:

Przewidywalne zachowanie Każde uruchomienie daje taki sam rezultat (sukces

lub porażka)

Wykonanie kodu na wielu wątkach: przypadkowe za każdym razem inne

Trudno jest napisać testy do kodu wielowątkowego

CHESS – projekt Microsoft Research Próba okiełznania przypadkowości Zarządzanie przeplotem między wątkami Uruchomienie testu na różnych kombinacjach

przeplotu Możliwość odtworzenia przeplotu!

Tylko Visual Studio 2008 Prawdopodobnie jako część Visual Studio 11

Jak pisać testy jednostkowe? Kod „jednowątkowy” można łatwo testować

Kod wielowątkowy testować jest trudno

A gdyby tak pozbyć się równoległości?

Przykład

Testowanie koduwielowątkowego

Testowanie – czego można się „pozbyć”? Konstrukcje wielowątkowe:

ThreadPool.QueueUserWorkItem Thread.Start Task (TaskScheduler) Timer

Wzorce: nieskończona pętla warunek

Podsumowanie

Podsumowanie Uwaga na częste błędy! Warto przemyśleć wielowątkowość w aplikacji Kod prosty, przejrzysty i udokumentowany Nie da się przetestować bezpieczeństwa

wielowątkowego Da się testować kod wielowątkowy – wystarczy

pozbyć się wielowątkowości!

Błędy - disclaimer

Nie róbcie tego w domu!Ani w pracy!

Zawodowcy popełnili za Was te błędy,

żebyście Wy nie musieli.

Przydatne odnośniki Events:

http://blogs.msdn.com/b/cburrows/archive/2010/03/05/events-get-a-little-overhaul-in-c-4-part-i-locks.aspx

http://blogs.msdn.com/b/cburrows/archive/2010/03/08/events-get-a-little-overhaul-in-c-4-part-ii-semantic-changes-and.aspx

http://blogs.msdn.com/b/cburrows/archive/2010/03/18/events-get-a-little-overhaul-in-c-4-part-iii-breaking-changes.aspx

http://blogs.msdn.com/b/cburrows/archive/2010/03/30/events-get-a-little-overhaul-in-c-4-afterward-effective-events.aspx

Dziękuję za uwagę. Pytania?