Programowanie współbieżne #1

Dlaczego warto zainteresować się tematem programowania współbieżnego? Przede wszystkim dlatego, że towarzyszy nam ono podczas budowania zdecydowanej większości systemów! To ważny element warsztatu programisty, który przyda się:

  • w aplikacjach desktopowych
  • w aplikacjach mobilnych
  • w aplikacjach webowych
  • programując gry
  • przetwarzając duże zbiory danych

Niektóre z tych punktów, na pierwszy rzut oka, mogą wydawać się nieoczywiste. Prawdą jest, że w zależności od tego jaką aplikację tworzymy, ze współbieżnością będziemy mieć do czynienia bardziej lub mniej świadomie. Przyczyny, dla których stosuje się rozwiązania współbieżne, czy też korzyści z nich wynikające, to przede wszystkim:

  • Zwiększenie dostępności (z perspektywy użytkownika, innego serwisu, itp.)
  • Zwiększenie wydajności
  • Możliwość lepszego odwzorowania procesów, które w świecie rzeczywistym zachodzą współbieżnie

Współbieżność a równoległość

Zanim przejdziemy do szczegółowego omówienia korzyści wynikających z realizacji implementacji współbieżnych, zwróćmy uwagę na rozróżnienie dwóch pojęć, które często pojawiają się razem. Chodzi mianowicie o różnice pomiędzy współbieżnością a równoległością. Dość dobrze obrazuje ją poniższy rysunek:

Przetwarzanie współbieżne vs równoległe

Umieszczona po środku oś reprezentuje czas, zaś kolorowe prostokąty wykonanie różnych zadań. Na górnej części diagramu przedstawiony został model wykonania współbieżnego, ale nie równoległego. Przykładem z życia codziennego może być sytuacja, w której podczas popołudniowego deseru pijemy kawę i rozmawiamy. Nie wykonujemy tych czynności w tym samym momencie (groziłoby to zaksztuszeniem) ale naprzemiennie. W efekcie przez cały czas podwieczorku zarówno pijemy kawę jak i uczestniczymy w rozmowie. Dolna część diagramu prezentuje wykonanie równoległe. Widzimy na niej, że poszczególne zadania są realizowane dokładnie w tej samej chwili. Przykładem „naszej równoległości” może być prowadzenie rozmowy podczas spaceru. Jednocześnie opowiadamy znajomym o ekscytującym filmie, który widzieliśmy w poprzedni piątek, oraz wykonujemy kolejne kroki. Co ważne, wykonanie równoległe jest równocześnie wykonaniem współbieżnym (ale nie na odwrót – nie każda współbieżność wymaga równoległości).

Aplikacje desktopowe oraz mobilne

Jaki jest jeden z najmniej lubianych przez użytkowników stan aplikacji? „Wisi”. Moment, w którym cały interfejs użytkownika, czy to aplikacji desktopowej czy mobilnej, zamarza i nie reaguje na żadne bodźce. Dłuższe trwanie takiego stanu wyprowadzi z równowagi nawet najbardziej cierpliwych użytkowników i zapewne zakończy się próbą ubicia krnąbrnej aplikacji lub też twardym restartem całego systemu (gdy nic innego nie pomaga). Czy w takich chwilach procesor po prostu stwierdził, że ma dość i potrzebuje wakacji, bezlitośnie ignorując wszystkie płynące od użytkownika wezwania? Na szczęście nie 🙂 Po prostu był zajęty czymś innym. Przetwarzaniem innego zadania (albo – zdecydowanie częściej – czekaniem na jego wyniki), które niestety się przeciągnęło. To tak jakbyśmy, wracając do metafory rozmowy, przez całe pół godziny sączyli lemoniadę przez słomkę, a następnie wylali z siebie potok słów odpowiadając na wszystkie zadane do tej pory pytania ze strony naszego rozmówcy. Oczywiście o ile byłoby jeszcze komu odpowiadać, a „użytkownik” nie zdecydowałby się wcześniej nas zrestartować 😉 Typowa interakacja użytkownika z aplikacją polega na realizacji wielu krótkich (z punktu widzenia procesora) zadań. Może to być np. przesunięcie kursora myszki, zmiana stanu ikony na ekranie itp. Część operacji jest niestety niemożliwa do szybkiego zakończenia – np. pobieranie pliku z sieci. Jeżeli oddelegowalibyśmy cały procesor do tego zadania to mamy w aplikacji freeze. Aby program działał poprawnie, powinien na bieżąco obsługiwać interakcje z użytkownikiem, a czas pomiędzy nimi przeznaczyć na inne zadania. I tu właśnie na scenę wkracza współbieżność. Praktycznie każda aplikacja desktopowa lub mobilna jest aplikacją współbieżną – właśnie ze względu na konieczność utrzymania wysokiej dostępności. W aplikacjach z interfejsem użytkownika najczęściej wykorzystuje się koncept tzw. pętli zdarzeń wraz z całkowitym zakazem wykonywania długich, blokujących operacji w wątku obsługującym interakcje. Bardziej szczegółowo na temat tego i innych modeli programowania współbieżnego opowiem w oddzielnym wpisie z cyklu.

Aplikacje webowe

Poszukiwania współbieżności w aplikacjach webowych należy rozpocząć od przeanalizowania zasady działania serwera webowego. Jego zadaniem jest obsłużenie wysyłanych do niego żądań. Proces polega na przyjęciu żądania, wykonania kodu realizującego odpowiedni fragment logiki biznesowej i odesłaniu odpowiedzi. Żądania mogą pochodzić z wielu niezależnych od siebie klientów (np. przeglądarek internetowych różnych użytkowników) i dla większości aplikacji standardem będzie sytuacja, w której następne żądanie pojawia się jeszcze przed zakończeniem przetwarzania poprzedniego. Zdecydowana większość odpowiedzi generowana będzie stosunkowo szybko, chociaż raz na jakiś czas zapewne trafi się bardziej wymagające żądanie. Stosując programowanie współbieżne (np. uruchamiając wiele wątków przetwarzania w obrębie naszego serwera) zachowamy wysoką dostępność usługi również w tej sytuacji. W podejściu sekwencyjnym wszyscy inni klienci zderzyliby się z nieoczekiwanym wydłużeniem czasu ładowania odpowiedzi, wynikającym z przetwarzania felernego żądania. Wraz ze wzrostem skali działania naszej aplikacji liczba użytkowników, a tym samym i potrzebnej mocy obliczeniowej, może przekroczyć możliwości pojedynczego procesora. Obsługa setek tysięcy żądań na sekundę nie byłaby możliwa, gdyby nie zrównoleglenie ich wykonania na wielu rdzeniach, a często i fizycznych maszynach. W tym wypadku mamy więc do czynienia z korzyścią zarówno w obszarze dostępności aplikacji, jak i ogólnej wydajności.

Przetwarzanie dużych zbiorów danych

Problemy tej kategorii wymagają pokaźnej mocy obliczeniowej i często przeliczenie ich na pojedynczej, nawet bardzo zaawansowanej jednostce, zajęłoby nieakceptowalnie długi czas. Na szczęście zazwyczaj charakteryzują się one również wysokim potencjałem do zrównoleglania, pozwalając na zastosowanie tego podejścia i wielokrotnego przyśpieszenia obliczeń. Przykładem tego typu zadań może być realizacja symulacji naukowych albo wyznaczenie najbardziej odpowiedniej reklamy dla każdego z użytkowników Facebooka 😉

Współbieżność w Pythonie

Współbieżność jest ważna i wszechobecna. Dobrze byłoby zatem, aby język programowania wspierał ją w możliwie największym zakresie. Jak wygląda sytuacja na „naszym podwórku”? Tym tematem zajmiemy się w następnych wpisach z cyklu. W najbliższym postaram się wyjaśnić fakty i rozwiać kilka mitów dotyczących koncepcji Global Interpreter Lock, czyli tzw. GILa w Pythonie. Hej! 🙂