Na początku FORTRAN i BASIC zasadniczo wszystkie programy były pisane przy pomocy instrukcji GOTO. W rezultacie powstał kod spaghetti, a rozwiązaniem było ustrukturyzowane programowanie.
Podobnie wskaźniki mogą mieć trudne do kontrolowania cechy w naszych programach. C ++ zaczęło się od wielu wskaźników, ale zalecane jest użycie referencji. Biblioteki takie jak STL mogą zmniejszyć część naszej zależności. Istnieją również idiomy do tworzenia inteligentnych wskaźników, które mają lepszą charakterystykę, a niektóre wersje C ++ pozwalają na odwołania i kod zarządzany.
Praktyki programowania, takie jak dziedziczenie i polimorfizm, wykorzystują wiele wskazówek za kulisami (tak jak w przypadku, gdy programowanie strukturalne generuje kod wypełniony instrukcjami rozgałęzienia). Języki takie jak Java eliminują wskaźniki i używają funkcji wyrzucania elementów bezużytecznych do zarządzania dynamicznie przydzielanymi danymi zamiast polegać na programistach w celu dopasowania wszystkich swoich nowych instrukcji usuwania i usuwania.
W swoim czytaniu widziałem przykłady programowania wieloprocesowego i wielowątkowego, które nie wykorzystują semaforów. Czy używają tego samego pod różnymi nazwami, czy też mają nowe sposoby strukturyzacji ochrony zasobów przed jednoczesnym użyciem?
Na przykład konkretnym przykładem systemu do programowania wielowątkowego z procesorami wielordzeniowymi jest OpenMP. Reprezentuje region krytyczny w następujący sposób, bez użycia semaforów, które wydają się nie być zawarte w środowisku.
th_id = omp_get_thread_num();
#pragma omp critical
{
cout << "Hello World from thread " << th_id << '\n';
}
Ten przykład jest fragmentem: http://en.wikipedia.org/wiki/OpenMP
Alternatywnie, podobna ochrona wątków przed sobą za pomocą semaforów z funkcjami wait () i signal () może wyglądać następująco:
wait(sem);
th_id = get_thread_num();
cout << "Hello World from thread " << th_id << '\n';
signal(sem);
W tym przykładzie rzeczy są dość proste i wystarczy prosta recenzja, aby pokazać, że połączenia wait () i signal () są dopasowane, a nawet przy dużej współbieżności zapewnione jest bezpieczeństwo wątków. Ale inne algorytmy są bardziej skomplikowane i wykorzystują wiele semaforów (zarówno binarnych, jak i zliczających) rozmieszczonych w wielu funkcjach ze złożonymi warunkami, które mogą być wywoływane przez wiele wątków. Trudno poradzić sobie z konsekwencjami tworzenia impasu lub braku zapewnienia bezpieczeństwa wątków.
Czy takie systemy jak OpenMP eliminują problemy z semaforami?
Czy przenoszą problem gdzieś indziej?
Jak przekształcić mój ulubiony semafor za pomocą algorytmu, aby nie używać już semaforów?
źródło
Odpowiedzi:
Czy istnieją równoległe techniki programowania i praktyki, których nie należy już stosować? Powiedziałbym tak .
Jedną z technik wczesnego programowania współbieżnego, która obecnie wydaje się rzadka, jest programowanie oparte na przerwie . Tak działał UNIX w latach siedemdziesiątych. Zobacz komentarz Lions dotyczący systemu UNIX lub Bach's Design systemu operacyjnego UNIX . W skrócie, technika polega na tymczasowym zawieszeniu przerwań podczas manipulowania strukturą danych, a następnie przywróceniu przerwań. Strona podręcznika użytkownika BSD spl (9)ma przykład tego stylu kodowania. Zauważ, że przerwania są zorientowane sprzętowo, a kod zawiera ukrytą zależność między rodzajem przerwania sprzętowego a strukturami danych powiązanymi z tym sprzętem. Na przykład kod, który manipuluje dyskowymi buforami we / wy, musi zawieszać przerwania na sprzęcie kontrolera dysku podczas pracy z tymi buforami.
Ten styl programowania stosowały systemy operacyjne na sprzęcie jednoprocesorowym. Znacznie rzadziej aplikacje radziły sobie z przerwaniami. Niektóre systemy operacyjne miały przerwania programowe i myślę, że ludzie próbowali zbudować na nich systemy wątków lub coroutine, ale nie było to zbyt rozpowszechnione. (Z pewnością nie w świecie UNIX.) Podejrzewam, że programowanie w stylu przerwania jest obecnie ograniczone do małych systemów wbudowanych lub systemów czasu rzeczywistego.
Semafory są postępem w stosunku do przerwań, ponieważ są konstrukcjami oprogramowania (niezwiązanymi ze sprzętem), zapewniają abstrakcje w stosunku do urządzeń sprzętowych oraz umożliwiają wielowątkowość i wieloprocesowość. Głównym problemem jest to, że są nieustrukturyzowane. Programista jest odpowiedzialny za utrzymanie relacji między każdym semaforem a strukturami danych, które chroni, globalnie w całym programie. Z tego powodu myślę, że gołe semafory są dziś rzadko używane.
Kolejnym małym krokiem naprzód jest monitor , który hermetyzuje mechanizmy kontroli współbieżności (blokady i warunki) z chronionymi danymi. Zostało to przeniesione do systemu Mesa (link alternatywny) , a stamtąd do Java. (Jeśli przeczytasz ten artykuł Mesa, zobaczysz, że blokady i warunki Javy są kopiowane niemal dosłownie z Mesa.) Monitory są pomocne w tym, że wystarczająco ostrożny i sumienny programista może bezpiecznie pisać współbieżne programy przy użyciu tylko lokalnego rozumowania o kodzie i danych w monitorze.
Istnieją dodatkowe konstrukcje bibliotek, takie jak te w
java.util.concurrent
pakiecie Java , które obejmują wiele wysoce współbieżnych struktur danych i konstrukcji puli wątków. Można je połączyć z dodatkowymi technikami, takimi jak ograniczanie nici i skuteczna niezmienność. Patrz Java Concurrency In Practice autorstwa Goetza i in. glin. do dalszej dyskusji. Niestety, wielu programistów wciąż rozwija własne struktury danych z blokadami i warunkami, kiedy naprawdę powinni używać czegoś takiego jak ConcurrentHashMap, w którym autorzy bibliotek już wykonali ciężkie podnoszenie.Wszystko powyżej ma kilka istotnych cech: mają wiele wątków kontroli, które oddziałują na globalnie wspólny, zmienny stan . Problem polega na tym, że programowanie w tym stylu jest nadal bardzo podatne na błędy. Niewielki błąd jest dość łatwy do niezauważenia, co powoduje złe zachowanie, które jest trudne do odtworzenia i zdiagnozowania. Być może żaden programista nie jest „wystarczająco ostrożny i pracowity”, aby opracować w ten sposób duże systemy. Przynajmniej bardzo niewielu jest. Powiedziałbym więc, że należy w miarę możliwości unikać programowania wielowątkowego ze wspólnym, zmiennym stanem.
Niestety nie jest całkowicie jasne, czy można tego uniknąć we wszystkich przypadkach. Wiele programów wciąż jest wykonywanych w ten sposób. Byłoby miło, gdyby to zostało wyparte przez coś innego. Odpowiedzi Jarroda Robersona i Davidk01 wskazują na takie techniki, jak niezmienne dane, programowanie funkcjonalne, STM i przekazywanie wiadomości. Jest ich wiele do polecania, a wszystkie są aktywnie rozwijane. Ale nie sądzę, aby w pełni zastąpiły dobry, staromodny, wspólny, zmienny stan.
EDYCJA: oto moja odpowiedź na konkretne pytania na końcu.
Nie wiem dużo o OpenMP. Mam wrażenie, że może być bardzo skuteczny w przypadku bardzo równoległych problemów, takich jak symulacje numeryczne. Ale to nie wydaje się ogólnego zastosowania. Konstrukty semaforów wydają się dość niskie i wymagają od programisty utrzymywania relacji między semaforami a udostępnianymi strukturami danych, ze wszystkimi problemami, które opisałem powyżej.
Jeśli masz algorytm równoległy, który używa semaforów, nie znam żadnych ogólnych technik przekształcania go. Być może będziesz w stanie przekształcić go w obiekty, a następnie zbudować wokół niego abstrakcje. Ale jeśli chcesz użyć czegoś takiego jak przekazywanie wiadomości, myślę, że naprawdę musisz ponownie zrozumieć cały problem.
źródło
Odpowiedz na pytanie
Ogólny konsensus jest taki, że wspólnym stanem mutable jest Bad ™, a stanem niezmiennym jest Good ™, który okazuje się być dokładny i prawdziwy wielokrotnie w językach funkcjonalnych i imperatywnych.
Problem polega na tym, że imperatywne języki głównego nurtu po prostu nie są zaprojektowane do obsługi tego sposobu pracy, rzeczy nie zmienią się dla tych języków w ciągu nocy. To jest wada porównania do
GOTO
. Niezmienny stan i przekazywanie wiadomości to świetne rozwiązanie, ale nie jest to również panaceum.Błędna przesłanka
To pytanie opiera się na porównaniach do błędnej przesłanki; taki
GOTO
był faktyczny problem i został powszechnie uznany przez Intergalatic Universal Board of Language Designers and Software Engineering Unions ©! BezGOTO
mechanizmu ASM w ogóle nie działałby. To samo z założeniem, że surowe wskaźniki są problemem w C lub C ++, a niektóre, jak inteligentne wskaźniki są panaceum, nie są.GOTO
nie był problemem, problem stanowili programiści. To samo dotyczy wspólnego stanu mutable . To samo w sobie nie stanowi problemu , problem stanowią programiści. Jeśli byłby sposób na wygenerowanie kodu, który używał współdzielonego stanu zmiennego w sposób, który nigdy nie miał żadnych warunków wyścigu ani błędów, to nie byłoby problemu. Podobnie jak w przypadku, gdy nigdy nie piszesz kodu spaghetti zGOTO
konstrukcjami równoważnymi, nie stanowi to również problemu.Edukacja to panaceum
Programiści Idiot są jakie były
deprecated
, każdy popularny język wciąż maGOTO
konstrukcję bezpośrednio lub pośrednio i to jestbest practice
, gdy stosowane właściwie w każdym języku, który ma tego typu konstrukcji.PRZYKŁAD: Java ma etykiety i
try/catch/finally
oba z nich działają bezpośrednio jakoGOTO
instrukcje.Większość programistów Java, z którymi rozmawiam, nawet nie wie, co
immutable
tak naprawdę oznacza poza nimi powtarzanie sięthe String class is immutable
z oczami przypominającymi zombie. Zdecydowanie nie wiedzą, jakfinal
prawidłowo użyć słowa kluczowego, aby utworzyćimmutable
klasę. Jestem więc całkiem pewien, że nie mają pojęcia, dlaczego przekazywanie wiadomości za pomocą niezmiennych wiadomości jest tak wspaniałe i dlaczego stan wspólnego mutable nie jest tak świetny.źródło
Najnowszą modą w kręgach akademickich wydaje się być Software Transactional Memory (STM), która obiecuje usunąć wszystkie włochate szczegóły programowania wielowątkowego z rąk programistów za pomocą wystarczająco inteligentnej technologii kompilatora. Za kulisami wciąż są zamki i semafory, ale jako programista nie musisz się o to martwić. Korzyści z takiego podejścia wciąż nie są jasne i nie ma oczywistych pretendentów.
Erlang używa przekazywania komunikatów i agentów do współbieżności i jest to prostszy model do pracy niż STM. Przy przekazywaniu wiadomości nie musisz się martwić o blokady i semafory, ponieważ każdy agent działa we własnym mini-wszechświecie, więc nie ma żadnych warunków wyścigowych związanych z danymi. Nadal masz dziwne przypadki na krawędzi, ale nie są one tak skomplikowane jak blokady na żywo i impasy. Języki JVM mogą korzystać z Akka i czerpać wszystkie korzyści z przekazywania wiadomości i aktorów, ale w przeciwieństwie do Erlanga, JVM nie ma wbudowanego wsparcia dla aktorów, więc pod koniec dnia Akka nadal korzysta z wątków i blokad, ale Ty jako programista nie musi się tym martwić.
Innym znanym mi modelem, w którym nie stosuje się blokad i wątków, jest stosowanie kontraktów futures, które są tak naprawdę kolejną formą programowania asynchronicznego.
Nie jestem pewien, ile tej technologii jest dostępne w C ++, ale są szanse, że jeśli zobaczysz coś, co nie używa jawnie wątków i blokad, będzie to jedna z powyższych technik zarządzania współbieżnością.
źródło
Myślę, że chodzi głównie o poziomy abstrakcji. Dość często w programowaniu przydatne jest wyodrębnienie niektórych szczegółów w sposób bezpieczniejszy lub bardziej czytelny lub coś w tym rodzaju.
Dotyczy to struktur kontrolnych:
if
s,for
si parzystetry
-catch
bloki są jedynie abstrakcjami nadgoto
s. Te abstrakcje są prawie zawsze przydatne, ponieważ zwiększają czytelność kodu. Ale zdarzają się przypadki, w których będziesz nadal musiał korzystaćgoto
(np. Jeśli piszesz ręcznie asembler).Dotyczy to również zarządzania pamięcią: inteligentne wskaźniki C ++ i GC są abstrakcjami w stosunku do surowych wskaźników i ręcznego usuwania / alokacji pamięci. A czasem te abstrakcje są nieodpowiednie, np. Gdy naprawdę potrzebujesz maksymalnej wydajności.
To samo dotyczy wielowątkowości: rzeczy takie jak futures i aktorzy to tylko abstrakcje nad wątkami, semaforami, muteksami i instrukcjami CAS. Takie abstrakcje mogą pomóc ci uczynić kod znacznie bardziej czytelnym, a także pomogą ci uniknąć błędów. Ale czasami są po prostu nieodpowiednie.
Powinieneś wiedzieć, jakie masz dostępne narzędzia i jakie są ich zalety i wady. Następnie możesz wybrać poprawną abstrakcję dla swojego zadania (jeśli istnieje). Wyższe poziomy abstrakcji nie umniejszają niższych poziomów, zawsze będą przypadki, w których abstrakcja nie jest odpowiednia, a najlepszym wyborem jest użycie „starej drogi”.
źródło
Tak, ale prawdopodobnie nie spotkasz niektórych z nich.
W dawnych czasach powszechne było stosowanie metod blokowania (synchronizacja barier), ponieważ pisanie dobrych muteksów było trudne. Nadal można zobaczyć ślady tego w różnych rzeczach, ponieważ niedawne korzystanie z nowoczesnych bibliotek współbieżności zapewnia znacznie bogatszy i dokładnie przetestowany zestaw narzędzi do równoległości i koordynacji między procesami.
Podobnie starszą praktyką było pisanie torturującego kodu, aby można było dowiedzieć się, jak go zrównoleglić ręcznie. Ta forma optymalizacji (potencjalnie szkodliwej, jeśli się pomylisz) również w dużej mierze wyszła poza okno wraz z pojawieniem się kompilatorów, które robią to za Ciebie, w razie potrzeby rozwijając pętle, przewidując następujące gałęzie itp. To jednak nie jest nowa technologia , co najmniej 15 lat na rynku. Korzystanie z takich rzeczy, jak pule wątków, pozwala także ominąć niektóre naprawdę podstępne kody z przeszłości.
Być może przestarzałą praktyką jest samodzielne pisanie kodu współbieżności zamiast korzystania z nowoczesnych, dobrze przetestowanych bibliotek.
źródło
Grand Central Dispatch firmy Apple to elegancka abstrakcja, która zmieniła moje myślenie o współbieżności. Z mojego skromnego doświadczenia wynika, że skupienie się na kolejkach upraszcza implementację logiki asynchronicznej o rząd wielkości.
Kiedy programuję w środowiskach, w których jest on dostępny, zastąpił większość moich zastosowań wątków, blokad i komunikacji między wątkami.
źródło
Jedną z głównych zmian w programowaniu równoległym jest to, że procesory są niesamowicie szybsze niż wcześniej, ale aby osiągnąć tę wydajność, wymagają ładnie zapełnionego bufora. Jeśli spróbujesz uruchomić kilka wątków jednocześnie, ciągle między nimi przełączając się, prawie zawsze unieważniasz pamięć podręczną dla każdego wątku (tj. Każdy wątek wymaga różnych danych do działania) i kończysz się zabijaniem wydajności znacznie bardziej niż ty przyzwyczajony do wolniejszych procesorów.
Jest to jeden z powodów, dla których frameworki asynchroniczne lub oparte na zadaniach (np. Grand Central Dispatch lub Intel TBB) są bardziej popularne, uruchamiają zadania kodu 1 na raz, kończąc je przed przejściem do następnego - jednak trzeba je zakodować każde zadanie zajmuje niewiele czasu, chyba że chcesz zepsuć projekt (tzn. zadania równoległe są naprawdę ustawione w kolejce). Zadania intensywnie wykorzystujące procesor są przekazywane do alternatywnego rdzenia procesora, a nie przetwarzane w jednym wątku, przetwarzając wszystkie zadania. Łatwiej jest także zarządzać, jeśli nie dzieje się tak naprawdę przetwarzanie wielowątkowe.
źródło