Monthly archive for March, 2008

Asynchroniczność kontra wątki

2008-03-10 23:02

Niekiedy trzeba zrobić coś czasochłonnego: operację, która nie zakończy się od razu, lecz zabierze zauważalny odcinek czasu. Dość często dotyczy to odczytu (lub zapisu) danych z miejsca, które nie musi być natychmiast dostępne: gniazdka sieciowego, międzyprocesowego potoku (pipe) czy w niektórych sytuacjach nawet pamięci dyskowej. Wówczas rzadko możemy pozwolić sobie na “powieszenie” programu na parę(naście/dziesiąt) sekund w oczekiwaniu, aż zlecona operacja się zakończy. W międzyczasie trzeba bowiem wykonywać też inne czynności, z aktualizacją interfejsu użytkownika na czele.

Typowy rozwiązaniem jest wtedy umieszczenie czasochłonnej czynności w osobnym wątku. Zdarza się jednak, że nie jest to jedyne wyjście. Niekiedy – na przykład przy korzystaniu z gniazd sieciowych w Windows API lub dowolnych strumieni w .NET – dysponujemy alternatywnym sposobem, którym jest zlecenie operacji asynchronicznej. Polega ono na żądaniu wykonania danego działania “w tle” wraz ze sposobem, w jaki chcemy odebrać informację zwrotną. W tym charakterze chyba najczęściej stosuje się funkcje typu callback, podawane – zależnie od języka – jako wskaźniki (C/C++), delegaci (Delphi, C#) lub obiekty implementujące ustalone interfejsy (Java). Po zakolejkowaniu takiego żądania program wykonuje się dalej bez żadnych przerw. Gdy zaś operacja zakończy się, nasz callback zostanie wywołany i w nim będzie można pobrać rezultaty zleconego zadania.
Brzmi całkiem nieźle, prawda? Właściwie można by powiedzieć, że to świetny sposób na uniknięcie stosowania tych strasznych wątków ;-) W praktyce trzeba jednak pamiętać o tym, że:

  • Brak wątków wcale nie oznacza, że nie wystąpią typowe dla wielowątkowości kłopoty – zwłaszcza problemy z synchronizacją dostępu do zasobów. Dotyczą one bowiem każdej sytuacji, kiedy to kod wykonujący się równoległe próbuje uzyskać dostęp do tych samych danych – niezależnie od tego, czy dotyczy to wątków, osobnych procesów ze współdzieloną pamięcią czy właśnie operacji asynchronicznych. Zresztą wewnątrz mechanizmu obsługi takich operacji równie dobrze siedzieć może zwyczajny wątek, który się nimi zajmuje w sposób przezroczysty.
  • Wywołania asynchroniczne “rozgałęziają” wykonywanie kodu w sposób bardziej subtelny (co oznacza: trudniejszy do śledzenia) niż wątki. W przypadku wątków zawsze dobrze widać miejsce rozpoczęcia i zakończenia pracy oraz całą te robotę, napisaną po kolei i w jednym miejscu. Jeśli zaś korzystamy z wywołań asynchronicznych, to kod obsługi wyniku operacji jest oddzielony od kodu, który tę operację zleca. To sprawia, że śledzenie i debugowanie może być trudniejsze i wymagać większej uwagi.

W sumie więc warto pamiętać o tym, że przy wprowadzaniu równoległości trzeba zawsze liczyć z dodatkowymi – nazwijmy to – “kwestiami do rozważenia” :] Unikanie tworzenia wątków za wszelką cenę nie musi zatem być najlepszym wyjściem, skoro koszt rozwiązania alternatywnego bywa podobny.

Tags: ,
Author: Xion, posted under Programming » 5 comments

Procent od funkcji drukujących

2008-03-08 23:58

Wszyscy znamy funkcję printf – część standardowej biblioteki C – oraz jej liczne warianty z przedrostkami i przyrostkami, służące wypisywaniu tekstów do różnych miejsc na różne sposoby. Jeśli z nich korzystamy, to czasem zdarza się, że chcemy wydrukować komunikat dany jako pojedynczy, znany już napis. A wówczas można wyprodukować coś, co w najprostszej wersji będzie wyglądało tak:

  1. char* s;
  2. // ...
  3. printf (s);

Tak się jednak stringów nie wypisuje – nawet mimo tego, iż w większości przypadków działa to bez problemów. Możemy bowiem trafić na przypadek złośliwy, a błędy objawiające się tylko czasami są, jak wiemy, jednymi z najgorszych…

Rzecz w tym, że w rodzinie funkcji printfopodobnych za to, co wydrukujemy, odpowiadają dwie rzeczy. Drugą z nich jest lista danych, mogąca mieć dowolnie dużo elementów; stąd też funkcje te przyjmują zmienną liczbę argumentów. Ale pierwszą jest tak zwany format, który mówi, jak te elementy należy interpretować: jako liczby całkowite, zmiennoprzecinkowe czy w końcu napisy. Ten argument jest łańcuchem znaków, występuje przed pozostałymi i w odróżnieniu od nich jest obowiązkowy.
Wywołanie printf(s); w istocie oznacza więc, że s nie jest tekstem do wypisania, ale formatem służącym interpretacji ewentualnych dalszych parametrów. Skoro jednak kolejnych argumentów nie ma, to wydaje się, że nie ma też problemu – zwłaszcza, że w wyniku tekst spod s faktycznie jest wypisywany. Jest tak jednak tylko momentu, gdy natrafimy na łańcuch zawierający znaczek procenta (%).

Jak wiemy, format dla funkcji typu printf może bowiem zawierać (i zwykle zawiera) znaczniki odnoszące się do jej dalszych argumentów. Niemal zawsze wpisujemy je ręcznie w kodzie, bo dokładnie wiemy, co chcemy wydrukować – np.:

  1. int n;
  2. printf ("n = %i", n);

To sprawia, że bardzo łatwo zacząć je traktować identycznie jak sekwencje ucieczki, czyli podciągi \n, \t, itd., zamieniane przez kompilator na odpowiadające im znaki (tutaj: końca wiersza i tabulatora). W wynikowych stringach nie ma więc po nich śladu, zamiast tego są odpowiednie znaki, których nie da się normalnie wpisać z klawiatury.
Ale znaczniki formatujące nie są interpretowane przez kompilator. Podczas działania programu w tym łańcuchu nadal siedzi %d, %f i każdy inny znacznik rozpoczynający się od procenta. Jeśli więc łańcuch s z wywołania printf(s); przypadkiem zawiera znak procenta, to funkcja mylnie potraktuje go i znaki po nim występujące jako znacznik formatujący. Zachowanie może być wtedy różne – w najlepszym wypadku ów procent i następny znak zostaną po prostu “zjedzone” – ale zawsze będzie różne od naszych oczekiwań.

Konkluzja? Jest oczywiście taka, aby zawsze pamiętać o formacie i nawet jeśli wypisujemy “tylko” łańcuch znaków, umieścić w nim %s:

  1. printf ("%s", s);

Różnica mała, lecz ważna :)

Tags: ,
Author: Xion, posted under Programming » 3 comments

Oswajanie pingwina

2008-03-08 15:26

Kilka tygodni temu zostałem “delikatnie przekonany” przez tzw. wyższe czynniki (czyli, jak nietrudno się domyślić, studia) do zawarcia bliższej znajomości z systemem Linux. Nie jest oczywiście tak, że w ten sposób nawiązałem dopiero pierwszy poważny kontakt z tym systemem. O nie, wręcz przeciwnie. Dotąd jednak próby utrzymania bliższych relacji nie skutkowały wieloma sukcesami. Czyżby teraz miało być inaczej?…

To całkiem możliwe, bo przecież Linux jest systemem dla koneserów, którzy uwielbiają, jak to się ładnie określa, “dostosowywać system do swoich potrzeb”. Można zatem optymistycznie przypuszczać, że mimo niezbyt korzystnego pierwszego wrażenia produkt spod znaku pingwina będzie zyskiwał przy bliższym poznaniu. Jak dotąd okazało się to mniej więcej prawdą. W każdym razie upływający czas działa w tym przypadku zdecydowanie na korzyść.
Logo UbuntuLinuksy się bowiem zmieniają i to zazwyczaj na lepsze. Chociażby fakt, że istnieje taka dystrybucja jak Ubuntu, która po prostu działa i nie sprawia żadnych problemów np. ze sprzętem, zdecydowanie przemawia na ich korzyść. W końcu mało kto chciałby wpierw przekopywać się przez pliki konfiguracyjne i (częściej) grupy dyskusyjne, by skonfigurować poprawnie swoją kartę graficzną czy połączenie z Internetem. A zdaje mi się, że mniej więcej tak to wyglądało jeszcze kilka lat temu – naturalnie pod warunkiem, że było się nowicjuszem z dużą dozą samozaparcia, a nie linuksowym ekspertem, dla którego sprawa nie przedstawia większego problemu.

Tux i motylek MSNJednak trzeba od razu wyraźnie stwierdzić: ani Ubuntu, ani jakakolwiek inna dystrybucja Linuksa nie jest realną alternatywą dla wszystkich użytkowników Windows. I jest tak wcale nie dlatego, że do zastosowań domowych Linux jest systemem złym. Chodzi raczej o to, że z pewnego punktu widzenia jest on nawet zbyt dobry. Oferuje on po prostu za dużo, by początkujący użytkownik komputera mógł to wszystko udźwignąć. Co z tego, że okienka są przejrzyste i dobrze zorganizowane, a dostęp do dowolnych opcji nie sprawia problemu? Dla początkującego będzie to nadmiar szczegółów, który go bardzo szybko przytłoczy. Tak, Linux nie myśli za użytkownika i nie prowadzi go za rączkę kreatorami i dymkami z podpowiedziami – i chwała mu za to. Wiele osób uzna to jednak za wadę i będą miały sto procent racji.
Mimo to nie sądzę, aby można było uważać to za problem. Jako system do użytku osobistego Linux powinien bowiem celować raczej w zaawansowanych i ewentualnie średnio zaawansowanych użytkowników komputerów. Mówię oczywiście o takich osobach, które ani nie cierpią na wrodzoną pingwinofilię (bo ich przekonywać nie trzeba), ani nie są zagorzałymi zwolennikami Okienek, którzy nigdy zdania nie zmienią. Jeśli ktoś ma wątpliwości, czy takie osoby w ogóle istnieją, to informuję, że sam do takich właśnie użytkowników od dłuższego czasu należę ;P I dopiero teraz mogę ostrożnie powiedzieć, że Linux powoli staje się dla takich osób całkiem rozsądną alternatywą. Pewnie jednak broda Stallmana urośnie jeszcze o kilka długości, zanim system ten zacznie na poważnie podbierać użytkowników Windowsowi.

Ja sam muszę stwierdzić, że z Linuksem jak najbardziej dałoby się żyć na co dzień. Wymagałoby to oczywiście zmiany kilku nawyków, ale z punktu widzenia użytkownika jest jak najbardziej wykonalne. Niestety (a właściwie stety) jestem też programistą, i to gier (a przynajmniej lubię się tak nazywać ;D) – co sprawia, że myślenie o przesiadce w zasadzie nie wchodzi w grę.
Potrafię przy tym bez problemu podać trzy bardzo konkretne powody takiej postawy. Według istotności, nazywają się one kolejno: DirectX, Visual Studio i .NET. Wiem naturalnie, że istnieje przecież OpenGL. Jestem też całkowicie świadom istnienia niezłych środowisk programistycznych dla Linuksa, w rodzaju Eclipse, KDevelop, Anjuty czy vima + GCC (;-]). I wreszcie, nie umknęło mi również Mono. To wszystko bardzo piękne, ale, jak wiadomo, człowiek jest istotą leniwą, a przyzwyczajenie i wygoda to w tym przypadku niezwykle cenne wartości :)
To jednak nie powinno przeszkadzać w tym, aby znać i cenić dobre rozwiązania – nawet jeśli samemu się z nich (zbyt często) nie korzysta.

Tags:
Author: Xion, posted under Computer Science & IT, Thoughts » 9 comments

Wskaźnik to tylko adres

2008-03-06 21:01

Jak C++ długi i szeroki, wskaźniki zawsze sprawiają początkującym (i nawet tym nieco bardziej zaawansowanym) programistom pewne kłopoty. Sam widziałem to zdecydowanie za dużo razy :) Niby wszyscy wiedzą, że wskaźnik to takie coś, co – jak podpowiada sama nazwa – pokazuje na jakiś inny obiekt. Czyli jest to taka “zmienna, która pokazuje na inną zmienną”.
Przy tej interpretacji nie jest trudno zorientować się, że przekazując wskaźnik chociażby jako argument funkcji, pozwalamy jej modyfikować obiekt, na który ów wskaźnik pokazuje – bez kopiowania tego obiektu. Bardzo podobnie działają zresztą referencje w językach typu C#, Delphi, Java, itp. Dlatego też zdawałoby się, że można by je z powodzeniem utożsamiać ze wskaźnikami w C/C++.

Uważam, że nic bardziej mylnego! Pomijam już taki drobiazg, że nad wspomnianymi referencjami czuwa odśmiecacz pamięci, który zapobiega wyciekom, podczas gdy wskaźników nic takiego nie dotyczy. Różnica jest bowiem znacznie głębsza. Te referencje są trochę “magiczne” – w tym sensie, że, ściśle mówiąc, właściwie nie wiadomo, jak fizycznie one działają (albo raczej: nie ma potrzeby, aby to wiedzieć). Grunt, że pokazują na jakiś obiekt, zaś obiekt ten może być wskazywany przez wiele referencji, a odwoływanie się do przez którąkolwiek z nich jest całkowicie równoważne.
W C/C++ w przypadku zwykłych wskaźników zasadniczo jest tak samo (z dokładnością do arytmetyki). Kłopoty zazwyczaj zaczynają się wtedy, gdy zostajemy uraczeni dwiema gwiazdkami i otrzymujemy wskaźnik na wskaźnik. Odpowiadająca mu “referencja do referencji” w C#, itp. jest bowiem czymś zupełnie bez sensu, jako że referencja nie jest przecież obiektem, na który można by pokazywać. I wówczas cała interpretacja wskaźników jako “czegoś, co w jakiś sposób pokazuje na coś” staje się co najmniej zastanawiająca.

A przecież dokładnie wiadomo, w jaki sposób wskaźniki ‘pokazują’. Przecież wskaźnik to nic innego, jak zmienna, która zawiera adres pewnego miejsca w pamięci. Zwykle adres ten odnosi się do innej zmiennej, obiektu, czasem funkcji, itd. I tyle, nie ma tutaj żadnego nadprzyrodzonego połączenia między wskaźnikiem a obiektem wskazywanym.
Ważne jest więc, by uświadomić sobie, że wskaźnik to zwykła zmienna zawierająca po prostu jakąś wartość (tutaj jest to pewien adres). To zaś oznacza, że sama ta zmienna również posiada jakieś miejsce w pamięci, czyli też rezyduje pod jakimś adresem. Kiedy pójdziemy za tym tokiem rozumowania, nie ma większych problemów z interpretacją podwójnych, potrójnych i wielokrotnych wskaźników.
A więc żadnej magii – to tylko liczby. Nie ma się czego bać :)

Tags: ,
Author: Xion, posted under Programming » 6 comments

Systemy kontroli wersji

2008-03-04 23:04

Kiedy nad jednym projektem pracuje więcej niż jedna osoba, nieuchronnie powstaną problemu z synchronizacją kodu napisanego przez różnych programistów. Jeśli wersje utrzymywane i modyfikowane przez poszczególne osoby będą przez jakiś (nawet całkiem krótki) czas odizolowane od siebie, wtedy nieuchronnie się “rozjadą”. Integracja takich kawałków będzie potem bardzo trudna.
Takich problemów nie da się rzecz jasna całkiem uniknąć, ale wymyślono kilka sposobów, które pomagają je rozwiązywać. Wśród nich pewnie najważniejsze są systemy kontroli wersji.

Logo TortoiseCVSIdea działania takiego systemu jest w miarę prosta. Istnieje mianowicie centralne i ogólnodostępne (czytaj: umieszczone na zdalnym serwerze) miejsce, gdzie składowane są różne wersje kodu projektu – czyli repozytorium. Pracujące nad nim osoby pobierają z niego aktualną wersję, dokonują swoich modyfikacji, a następnie załadowują ją z powrotem (tzw. commit), tworząc w ten sposób nową wersję w repozytorium. Po drodze mogą oczywiście wyniknąć konflikty, jeśli ten sam kod jest zmieniany równocześnie przez dwóch użytkowników; takie niezgodności są wykrywane automatycznie, ale ich rozwiązywanie należy już do programistów. Ponieważ jednak w repozytorium trzymana jest historia wszelkich zmian, zawsze można powrócić do poprzedniej wersji, jeśli coś pójdzie nie tak.
Logo SVNNajpopularniejszym systemem kontroli wersji jest oczywiście CVS (Concurrent Version System), o którym słyszał pewnie każdy. Pośród licznych jego wad największą jest chyba ta, iż jest… wciąż bardzo popularny :) Jest tak mimo tego, że od prawie dekady istnieje znacznie lepszy system o nazwie Subversion (w skrócie SVN), który ma liczne przewagi nad swoim poprzednikiem. Jak choćby to, że potrafi też wersjonować zmiany w strukturze katalogów, lepiej obsługiwać pliki binarne i nie grozić zepsuciem repozytorium, jeśli podczas commitu zdarzy się coś złego z serwerem lub połączeniem.

Jak to często bywa, przyczyną takiego stanu rzeczy jest naturalnie zasiedzenie tudzież przyzwyczajenie. Skoro bowiem tacy giganci jak SourceForge nadal opierają się na CVS-ie, prawdopodobnie jeszcze przez długi czas trzeba będzie radzić sobie z jego niedogodnościami. Jakby sama praca grupowa nie dostarczała wystarczającej liczby kłopotów… ;P

Tags:
Author: Xion, posted under Programming » 4 comments

Łapacz pakietów

2008-03-02 18:10

Oprogramowanie open source nie grzeszy zazwyczaj jakością, lecz od każdej reguły istnieją przecież wyjątki. Ostatnio znalazłem właśnie taki wyjątek; należy on do tej kategorii programów, które wymagają pewnego przygotowania, jeśli chcemy z nich korzystać efektywnie. Lecz mimo tego, iż jest to dość specjalistyczne narzędzie, nie sposób przy jego pomocy zrobić krzywdy swojemu systemowi. Warto się więc mu przyjrzeć, bo wśród tego rodzaju programów jest ono prawdopodobnie jednym z najlepszych.
Logo WiresharkaMam na tu na myśli analizator pakietów sieciowych Wireshark. Przy jego pomocy możemy bez większych problemów podejrzeć, cóż takiego jest przesyłane wzdłuż biegnących od naszego komputera kabli (względnie fal radiowych). Takie programy są popularnie nazywane snifferami i mogą służyć do bardzo wielu pożytecznych celów oraz kilku innych, mniej chwalebnych ;-)

Wśród wyróżniających cech Wiresharka trzeba na pewno wymienić interfejs, który jest przejrzysty, a przy tym funkcjonalny – co nie zdarza się często nawet wśród nie-GPL-owych programów. Bez większych problemów możemy złapane pakiety przeglądać i filtrować według wielu różnych kryteriów. Mogą one obejmować także cechy dot. specyficznych protokołów sieciowych (możemy np. wyświetlić pakiety HTTP z żądaniami typu GET). A tych wspieranych przez program jest zresztą całkiem sporo. Pakiety należące do znanych protokołów są oczywiście automatycznie rozkodowywane i pokazywane w przyjaznej postaci z podziałem na warstwy OSI, pola w nagłówkach oraz zasadniczą treść.

Screen z programu Wireshark

To oczywiście nie wszystko; do innych bardzo użytecznych funkcji należy chociażby możliwość wyodrębnienia całego strumienia TCP (czyli np. “rozmowy” jakiejś aplikacji ze zdalnym serwerem). Podobnie przydatnych narzędzi jest zresztą więcej i ciężko byłoby je wszystkie tu wymienić.
Dodatkowo program ten jest wybitnie wszędobylski i posiada równoważne wersje dla prawie każdego sensownego systemu operacyjnego, z Windows i przeróżnymi Linuksami włącznie. Biorąc pod uwagę to, że działa sprawnie, szybko i intuicyjnie, trzeba przyznać, że to nieoceniony instrument dla programisty piszącego aplikacje sieciowe.

Tags: , ,
Author: Xion, posted under Applications, Internet » 8 comments
 


© 2019 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.