Korzystanie z dodatkowych wątków i sterowanie wykonywaniem bieżącego pozwalają precyzyjnie zarządzać współbieżnym realizowaniem zadań. W Clojure możemy w tym celu użyć dodatkowych typów referencyjnych: Future, Promise i Delay. Istnieją również odpowiednie klasy Javy realizujące podobne cele, a nawet typ Volatile, który pozwala tworzyć szybkie choć niebezpieczne odpowiedniki zmiennych.
Współbieżność
Wykonywanie współbieżne (ang. concurrent) to cecha systemów i programów komputerowych, w których te same obliczenia dokonywane są jednocześnie (w tym samym czasie) przez więcej niż jeden komponent, przy opcjonalnej komunikacji między komponentami.
Więcej o współbieżności w Clojure można przeczytać w odcinku 12.
Future’y
Future to mechanizm asynchronicznego i równoległego realizowania zadań, w którym wyrażenie w celu obliczenia jego wartości jest przekazywane do innego, stworzonego specjalnie na tę potrzebę wątku, a po uzyskaniu wartości odniesienie do niej jest umieszczane w obiekcie referencyjnym, współdzielonym między wszystkimi wątkami.
Future’y obsługiwane są przez referencyjny typ danych o nazwie Future
. Użyjemy
ich na przykład wtedy, gdy pojawi się potrzeba wykonania pewnego fragmentu
programu równolegle, aby bieżący wątek mógł kontynuować działanie w trakcie
realizowania jakichś czasochłonnych obliczeń bądź operacji wejścia/wyjścia.
Użytkowanie Future’ów polega na utworzeniu odpowiednich obiektów
z przekazaniem im wyrażeń, których wartości zostaną obliczone w osobnym
wątku. Ostatnia z wartości stanie się współdzieloną wartością bieżącą obiektu
typu Future
. Do ustawienia stanu dojdzie tylko raz, bez możliwości
późniejszej aktualizacji referencji. Operacja tworzenia Future’a nie
zablokuje bieżącego wątku.
Próba odczytu wartości bieżącej Future’a spowoduje zablokowanie wątku,
w którym do niej doszło, do czasu, gdy będzie ona dostępna. Oznacza to, że jeżeli
zechcemy poznać wartość współdzieloną obiektu typu Future
, ale równolegle
realizowana zmiana stanu jeszcze się nie zakończyła (trwają obliczenia),
dereferencja będzie wiązała się z oczekiwaniem. Po zakończeniu obliczeń ich
wynik zostanie zapamiętany, więc każdy kolejny odczyt spowoduje skorzystanie
z niego bez blokowania.
Korzystając z Future’ów, nie możemy wybrać wątku, w którym wartościowane będą wyrażenia. Wiemy tylko, że będzie to wątek różny od bieżącego, w którym wykonany będzie podprogram. Po zakończeniu jego realizacji nie nastąpi powrót do kolejnego wyrażenia programu, lecz przetwarzanie zostanie zakończone, a dodatkowy wątek przeznaczony do zwolnienia.
Zazwyczaj obiekt typu Future
będzie dodatkowo w sposób stały utożsamiany
z symbolem określonym zmienną globalną lub
powiązaniem leksykalnym. W ten sposób można będzie
identyfikować go z użyciem nazwy widocznej w odpowiednich obszarach programu.
Obsługa błędów
W pewnych sytuacjach wartość bieżąca obiektu typu Future
może nie zostać nigdy
obliczona i ustawiona:
- gdy doszło do zawieszenia pracy podprogramu realizującego obliczenia (np. z powodu zapętlenia bądź innego błędu w algorytmie);
- gdy działający w osobnym wątku podprogram zgłosił wyjątek;
- gdy z użyciem
future-cancel
anulowano działanie podprogramu realizującego obliczenia.
W pierwszym z wymienionych przypadków sposobem obsługi sytuacji jest
zastosowanie funkcji deref
, która w wersji trójargumentowej
pozwala określić maksymalny czas oczekiwania na uzyskanie wartości podczas
dereferencji. Inny sposób to sprawdzanie, czy wartość bieżąca jest gotowa do
odczytu (funkcja realized?
), albo czy podprogram
realizujący obliczenia się zakończył (funkcja future-done?
).
Reakcją na błędnie działający podprogram może być na przykład przerwanie jego
pracy z użyciem funkcji future-cancel
.
W pozostałych dwóch przypadkach próby dereferencji będą powodowały zgłaszanie
wyjątków. Jeżeli realizację obliczeń anulowano, będzie to
java.util.concurrent.CancellationException
, natomiast w przypadku awaryjnego
przerwania inny wyjątek. Możemy je przechwytywać, albo przed próbą odczytu
wartości bieżącej korzystać z funkcji, które sprawdzą, czy da się to zrobić:
realized?
– aby sprawdzić czy ustawiono stan,
a future-cancelled?
– aby zbadać, czy doszło do
anulowania obliczeń.
Tworzenie Future’ów
Tworzenie obiektów typu Future
, future
Do tworzenia obiektów typu Future
służy makro future
.
Użycie:
(future & wyrażenie)
.
Makro przyjmuje zero lub więcej argumentów – wyrażeń, które będą wartościowane w osobnym wątku, a wartość ostatniego z nich stanie się bieżącą wartością powiązaną z tworzonym obiektem. Do czasu zakończenia obliczeń wartość ta pozostanie nieustalona.
Zwracaną wartością jest obiekt typu Future
, który po zakończeniu
wartościowania przekazanych wyrażeń będzie zawierał odniesienie do wartości
ostatniego z nich.
Jeżeli nie podano wyrażenia, wartością bieżącą, z którą powiązany zostanie
obiekt będzie nil
.
Wyrażenie z linii nr 1 tworzy obiekt referencyjny typu Future
, który odnosi
się do wartości nil
, ponieważ nie podano wyrażenia.
Wyrażenie z linii nr 4 otrzymuje wyrażenie będące wartością stałą, która stanie się wartością bieżącą obiektu po powiązaniu go z nią w osobnym wątku.
Kolejne wyrażenie (z linii nr 7) działa podobnie, jak powyższe, ale ustawioną wartością będzie 2, ponieważ jest to ostatnie przekazane wyrażenie.
W przykładzie z linii nr 10 mamy przekazanie wyrażenia, które sumuje liczby z pewnego przedziału.
Ostatni przykład pokazuje, że makro future
może generować efekty uboczne,
jeżeli podane wyrażenia je powodują.
Zauważmy, że w niektórych przypadkach widzimy diagnostyczny napis :pending
,
a w innych :ready
(będące wartościami elementu mapy o kluczu :status
). W ten
sposób reprezentowany jest status obiektu referencyjnego. Pierwsze słowo
kluczowe oznacza, że obliczenia są jeszcze wykonywane, a drugie, że praca
równoległego wątku zakończyła się i powiązano Future’a z wartością stanowiącą
rezultat obliczeń. W symbolicznie wyrażonej mapie widzimy też klucz :val
, do
którego przypisana jest wartość bieżąca obiektu typu Future
, jeśli już ją
obliczono.
Uwaga: Wywołania
future
sprawiają, że używana jest pula wątków, która nie będzie automatycznie zniszczona, gdy program zakończy pracę. Powoduje to, że proces maszyny wirtualnej nie zakończy się natychmiastowo, lecz po jednej minucie. Można to przyspieszyć, korzystając z funkcjishutdown-agents
.
Tworzenie obiektów typu Future
, future-call
Do tworzenia obiektów typu Future
może służyć również funkcja future-call
,
z której korzysta makro future
.
Użycie:
(future-call funkcja)
.
Funkcja przyjmuje jeden obowiązkowy argument, którym powinien być obiekt
funkcyjny. Podprogram zawartej w nim funkcji zostanie wywołany w osobnym
wątku, a zwracana wartość stanie się wartością bieżącą tworzonego obiektu typu
Future
. Do czasu zakończenia obliczeń wartość ta pozostanie nieustalona.
Funkcja zwraca obiekt typu Future
, który po zakończeniu równoległego wywołania
funkcji będzie zawierał odniesienie do zwróconej przez nią wartości.
Uwaga: Wywołania
future
sprawiają, że używana jest pula wątków, która nie będzie automatycznie zniszczona, gdy program zakończy pracę. Powoduje to, że proces maszyny wirtualnej nie zakończy się natychmiastowo, lecz po jednej minucie. Można to przyspieszyć, korzystając z funkcjishutdown-agents
.
Identyfikowanie Future’ów
Future’y są obiektami, z którymi, podobnie jak z innymi pamięciowymi
wartościami, można powiązać symboliczne identyfikatory i w ten sposób
określać ich tożsamości. Możemy więc stwarzać odnoszące się do obiektów typu
Future
globalne zmienne, jak również korzystać z innych
rodzajów powiązań (np. leksykalnych).
Pobieranie wartości
Odczytywanie wartości Future’ów, deref
Żeby odczytać wartość wskazywaną przez Future’a, możemy użyć funkcji deref
.
Użycie:
(deref future)
,(deref future czas-oczekiwania-ms wartość-po-czasie)
.
W wariancie jednoargumentowym przyjmowanym argumentem powinien być obiekt typu
Future
, a zwraca wartość, do której odniesienie jest przechowywane w tym
obiekcie, jeżeli została już ona obliczona. Jeśli obliczenia prowadzące do jej
uzyskania jeszcze się nie zakończyły, wątek zostanie zablokowany do chwili
pojawienia się rezultatu.
Wariant trójargumentowy deref
służy do odczytywania wartości bieżącej
z kontrolą maksymalnego czasu oczekiwania na jej uzyskanie. Drugim argumentem
powinna być wyrażona numerycznie całkowita liczba milisekund określająca czas,
po którym oczekiwanie zostanie przerwane i nastąpi wyjście z funkcji. Trzecim
obowiązkowym argumentem powinna być wartość, która zostanie zwrócona zamiast
wartości bieżącej obiektu typu Future
, jeżeli dojdzie do upłynięcia czasu
oczekiwania. Gdy uda się uzyskać wartość bieżącą przed upływem podanego czasu,
zostanie ona zwrócona.
Uwaga: Jeżeli podczas obliczeń przekazanych do
future
wyrażeń lub przekazanej dofuture-call
funkcji dojdzie do zgłoszenia wyjątku, każda próba odczytu wartości bieżącej będzie generowała ten wyjątek.
Z wyjątkiem będziemy też mieć do czynienia, gdy wartość nie zostanie ustawiona, ponieważ anulowano obliczenia z użyciem funkcjifuture-cancel
.
Dereferencja Future’ów, makro @
Makro czytnika @
(znak małpki) umieszczone przed wyrażeniem sprawia, że jeśli
zwracany przez nie obiekt jest typem referencyjnym, wywołana zostanie na nim
funkcja odpowiedzialna za odczyt wskazywanej wartości. W przypadku Future’ów
będzie to omówiona wyżej funkcja deref
w jej jednoargumentowym
wariancie.
Użycie:
@future
.
Uwaga: Jeżeli podczas obliczeń przekazanych do
future
wyrażeń lub przekazanej dofuture-call
funkcji dojdzie do zgłoszenia wyjątku, każda próba odczytu wartości bieżącej będzie generowała ten wyjątek.
Kontrolowanie wykonywania
Anulowanie pracy, future-cancel
Funkcja future-cancel
pozwala wymusić przerwanie pracy podprogramu
realizującego w osobnym wątku obliczenia, których rezultat miał stać się
wartością bieżącą obiektu typu Future
.
Użycie:
(future-cancel future)
.
Funkcja przyjmuje jeden argument, którym powinien być obiekt typu Future
,
a zwraca wartość true
, jeżeli dokonano anulowania przetwarzania, a false
,
gdy obliczenia zostały już zakończone lub anulowane i nie można przerwać pracy
realizującego je podprogramu, ponieważ nie jest on wykonywany.
Funkcja future-cancel
bywa przydatna tam, gdzie z powodu zapętlenia lub innych
okoliczności podane przy tworzeniu Future’a wyrażenie nie może być wartościowane
w rozsądnym czasie.
Aby sprawdzić, czy obliczenia zostały anulowane, można skorzystać z funkcji
future-cancelled?
.
Testowanie Future’ów
Testowanie typu, future?
Funkcja future?
służy do sprawdzania, czy podana wartość jest obiektem typu
Future
.
Użycie:
(future? wartość)
.
Pierwszym, obowiązkowym argumentem funkcji powinna być wartość, której typ zostanie sprawdzony.
Funkcja zwraca wartość true
, jeżeli podana wartość jest obiektem typu
Future
, a false
w przeciwnym razie.
Sprawdzanie zakończenia pracy, future-done?
Funkcja future-done?
służy do sprawdzania, czy realizowany w osobnym wątku
podprogram przeprowadzający obliczenia, które służą do uzyskania wartości
bieżącej Future’a, zakończył pracę.
Użycie:
(future-done? future)
.
Pierwszym, obowiązkowym argumentem funkcji powinien być obiekt typu Future
.
Funkcja zwraca wartość true
, jeżeli podprogram obliczający wartość zakończył
się, a false
w przeciwnym razie.
Uwaga: Wartość
true
zostanie zwrócona niezależnie od tego, czy realizacja obliczeń zakończyła się ustawieniem wartości bieżącej, czy też została przerwana.
Sprawdzanie uzyskania wyniku, realized?
Funkcja realized?
służy do sprawdzania, czy realizowany w osobnym wątku
podprogram przeprowadzający obliczenia, które służą do uzyskania wartości
bieżącej Future’a, zakończył się pomyślnie i ustawiono wartość bieżącą
referencji.
Użycie:
(realized? future)
.
Pierwszym, obowiązkowym argumentem funkcji powinien być obiekt typu Future
.
Funkcja zwraca wartość true
, jeżeli ustawiono wartość bieżącą, a false
w przeciwnym razie.
Sprawdzanie anulowania pracy, cancelled?
Funkcja future-cancelled?
służy do sprawdzania, czy realizowany w osobnym
wątku podprogram przeprowadzający obliczenia, które służą do uzyskania wartości
bieżącej Future’a, został przerwany zanim doszło do ustawienia wartości bieżącej
referencji.
Użycie:
(future-cancelled? future)
.
Pierwszym, obowiązkowym argumentem funkcji powinien być obiekt typu Future
.
Funkcja zwraca wartość true
, jeżeli przerwano wykonywanie podprogramu,
a false
w przeciwnym razie.
Promise’y
Typ Promise
służy do obsługi operacji, które powinny być realizowane
równolegle w ten sposób, że jeden lub więcej wątków oczekują na dostarczenie
wartości, która obliczana jest w innym wątku. Można powiedzieć, że jest to
mechanizm komunikacji międzywątkowej, która polega na ustawianiu wartości
współdzielonej Promise’a.
W przeciwieństwie do Future’ów wyrażenie, którego wartość stanie się wartością bieżącą obiektu, musi być już wcześniej obliczone w wybranym przez programistę wątku. Korzystanie z Promise’ów nie tworzy automatycznie nowych wątków, a ich obiekty inicjowane są przekazywanymi jako argumenty odpowiedniej funkcji stałymi wartościami, a nie wyrażeniami, które dopiero będą wartościowane.
Promise
to typ referencyjny, dzięki któremu daje się wyrazić
niekoordynowaną, pojedynczą zmianę współdzielonego stanu, do której już
doszło lub dopiero dojdzie w dowolnym wątku, zaś realizowanie obliczeń
prowadzących do uzyskania wartości odbędzie się w sposób asynchroniczny.
Próba odczytu wartości bieżącej obiektu typu Promise
spowoduje
zablokowanie bieżącego wątku do momentu jej uzyskania, natomiast zapis
jest operacją nieblokującą.
Wątek oczekujący na wartość może dokonać blokującej dereferencji, natomiast
wątek realizujący obliczenia tej wartości musi użyć funkcji dokonującej
powiązania z nią obiektu typu Promise
. Operacja ta może być przeprowadzona
tylko raz.
Każda kolejna (poza pierwszą) dereferencja Promise’a spowoduje zwrócenie wcześniej ustawionej wartości współdzielonej.
Tworzenie
Tworzenie obiektów typu Promise
, promise
Do tworzenia obiektów typu Promise
służy funkcja promise
.
Użycie:
(promise)
.
Funkcja nie przyjmuje żadnych argumentów, a zwraca obiekt typu Promise
,
którego wartość współdzieloną można ustawiać z wykorzystaniem funkcji
deliver
, a odczytywać z użyciem deref
lub
odpowiedniego makra czytnika.
Powyższe wyrażenie tworzy obiekt referencyjny typu Promise
, który oczekuje na
ustawienie wartości współdzielonej.
Identyfikowanie Promise’ów
Promise’y są obiektami, z którymi, podobnie jak z innymi pamięciowymi
wartościami, można powiązać symboliczne identyfikatory i w ten sposób
określać ich tożsamości. Możemy więc stwarzać odnoszące się do obiektów typu
Promise
globalne zmienne, jak również korzystać z innych
rodzajów powiązań (np. leksykalnych).
Pobieranie wartości
Odczytywanie wartości Promise’ów, deref
Żeby odczytać wartość wskazywaną przez Promise’a, możemy użyć funkcji deref
.
Użycie:
(deref promise)
,(deref promise czas-oczekiwania-ms wartość-po-czasie)
.
W wariancie jednoargumentowym przyjmowanym argumentem powinien być obiekt typu
Promise
, a zwraca wartość, do której odniesienie jest przechowywane w tym
obiekcie, jeżeli została już ona dostarczona. Jeśli nie ustawiono jeszcze
wartości współdzielonej Promise’a, wątek zostanie zablokowany do chwili jej
pojawienia się.
Wariant trójargumentowy deref
służy do odczytywania wartości bieżącej
z kontrolą maksymalnego czasu oczekiwania na jej uzyskanie. Drugim argumentem
powinna być wyrażona numerycznie całkowita liczba milisekund określająca czas,
po którym oczekiwanie zostanie przerwane i nastąpi wyjście z funkcji. Trzecim
obowiązkowym argumentem powinna być wartość, która zostanie zwrócona zamiast
wartości bieżącej obiektu typu Promise
, jeżeli dojdzie do upłynięcia czasu
oczekiwania. Gdy uda się uzyskać wartość bieżącą przed upływem podanego czasu,
zostanie ona zwrócona.
Dereferencja Promise’ów, makro @
Makro czytnika @
(znak małpki) umieszczone przed wyrażeniem sprawia, że jeśli
zwracany przez nie obiekt jest typem referencyjnym, wywołana zostanie na nim
funkcja odpowiedzialna za odczyt wskazywanej wartości. W przypadku Promise’ów
będzie to omówiona wyżej funkcja deref
w jej
jednoargumentowym wariancie.
Użycie:
@promise
.
Testowanie Promise’ów
Sprawdzanie dostarczenia wartości, realized?
Funkcja realized?
służy do sprawdzania, czy ustawiono współdzieloną wartość
bieżącą Promise’a.
Użycie:
(realized? promise)
.
Pierwszym, obowiązkowym argumentem funkcji powinien być obiekt typu Promise
.
Funkcja zwraca wartość true
, jeżeli ustawiono wartość bieżącą, a false
w przeciwnym razie.
Delay’e
Referencyjny typ Delay
służy do obsługi operacji, które powinny być odłożone
w czasie w celu realizacji w wybranym wątku. Możemy w ten sposób wyrażać
niekoordynowane, pojedyncze zmiany współdzielonych stanów. Aktualizacja
wartości współdzielonych odbywa się w sposób synchroniczny.
Podobnie jak w przypadku Future’ów, tworząc obiekty typu Delay
,
podajemy im wyrażenia, które nie będą natychmiast wartościowane. Różnica polega
na tym, że w przypadku Delay’ów nie będzie tworzony automatycznie nowy wątek,
lecz wyrażenie zostanie zapamiętane. Obliczanie jego wartości nastąpi
dopiero, gdy pojawi się pierwsza próba dereferencji (odczytu wartości bieżącej)
Delay’a.
Odczyty wartości bieżących obiektów typu Delay
są blokujące – pierwsze
odwołania do nich sprawiają, że w bieżącym wątku (w którym dokonywana jest
dereferencja) rozpoczyna się wykonywanie odłożonych wcześniej obliczeń. Z kolei
zapis (aktualizacja stanu Delay’ów) jest operacją nieblokującą –
podane wyrażenia są po prostu zapamiętywane.
Delay’ów użyjemy tam, gdzie chcemy odłożyć na później obliczenie wartości wyrażenia. Gdy już do tego dojdzie, obiekt referencyjny zostanie powiązany z uzyskaną w wyniku obliczeń stałą wartością, a kolejne funkcje dokonujące dereferencji będą ją zwracały, a nie przeliczały ponownie wyrażenia. Oznacza to, że do aktualizacji stanu Delay’a dojdzie tylko raz.
Zazwyczaj obiekt typu Delay
będzie dodatkowo w sposób stały utożsamiany
z symbolem określonym zmienną globalną lub
powiązaniem leksykalnym. W ten sposób można będzie
identyfikować go z użyciem nazwy widocznej w odpowiednich obszarach programu.
Tworzenie Delay’ów
Tworzenie obiektów typu Delay
, delay
Do tworzenia obiektów typu Delay
służy makro delay
.
Użycie:
(delay & wyrażenie)
.
Makro delay
przyjmuje zero lub więcej argumentów – wyrażeń, których
wartościowanie zostanie odłożone w czasie do momentu, gdy nastąpi próba
odczytania współdzielonej wartości bieżącej.
Makro zwraca obiekt typu Delay
, który po zakończeniu wartościowania
przekazanych wyrażeń będzie zawierał odniesienie do wartości ostatniego z nich.
Jeżeli nie podano wyrażenia, wartością bieżącą, z którą powiązany zostanie
obiekt będzie nil
.
Wyrażenie z linii nr 1 tworzy obiekt referencyjny typu Delay
, który odnosi
się do wartości nil
, ponieważ nie podano wyrażenia.
Wyrażenie z linii nr 4 otrzymuje wyrażenie będące wartością stałą, która stanie się wartością bieżącą obiektu po powiązaniu go z nią podczas pierwszej dereferencji.
Kolejne wyrażenie (z linii nr 7) działa podobnie, jak powyższe, ale ustawioną wartością będzie 2, ponieważ jest to ostatnie przekazane wyrażenie.
W przykładzie z linii nr 10 mamy przekazanie wyrażenia, które sumuje liczby z pewnego przedziału.
Ostatni przykład pokazuje, że przekazane wyrażenia mogą generować efekty uboczne.
Identyfikowanie Delay’ów
Delay’y są obiektami, z którymi, podobnie jak z innymi pamięciowymi
wartościami, można powiązać symboliczne identyfikatory i w ten sposób
określać ich tożsamości. Możemy więc stwarzać odnoszące się do obiektów typu
Delay
globalne zmienne, jak również korzystać z innych
rodzajów powiązań (np. leksykalnych).
Pobieranie wartości
Odczytywanie wartości Delay’ów, deref
Żeby odczytać wartość wskazywaną przez Delay’a, możemy użyć funkcji deref
.
Użycie:
(deref delay)
.
Funkcja przyjmuje jeden argument, którym powinien być obiekt typu Delay
,
a zwraca wartość, do której odniesienie jest przechowywane w tym obiekcie.
Jeżeli wartość nie została jeszcze obliczona, to w bieżącym wątku wartościowane
jest wyrażenie przekazane podczas tworzenia Delay’a.
Funkcja deref
w przypadku Delay’ów wywołuje force
na przekazanym
obiekcie.
Uwaga: Jeżeli podczas obliczeń przekazanych do
delay
wyrażeń dojdzie do zgłoszenia wyjątku, każda próba odczytu wartości bieżącej będzie generowała ten wyjątek.
Dereferencja Delay’ów, makro @
Makro czytnika @
(znak małpki) umieszczone przed wyrażeniem sprawia, że jeśli
zwracany przez nie obiekt jest typem referencyjnym, wywołana zostanie na nim
funkcja odpowiedzialna za odczyt wskazywanej wartości. W przypadku Delay’ów
będzie to omówiona wyżej funkcja deref
.
Użycie:
@delay
.
Kontrolowanie wykonywania
Wymuszanie wartości, force
Funkcja force
służy do odczytu wartości bieżącej obiektów typu Delay
lub
odczytu dowolnej innej wartości, którą podano (jeśli obiekt nie jest Delay’em).
Użycie:
(force wartość)
.
Funkcja przyjmuje dowolną wartość jako pierwszy argument i jeżeli jest to obiekt
typu Delay
, próbuje odczytać jego współdzieloną wartość bieżącą. Gdy nie
została ona jeszcze obliczona, wywołany będzie podprogram wartościujący
zapamiętane wcześniej wyrażenie, a obiekt typu Delay
zostanie powiązany
z uzyskanym rezultatem.
Wartością zwracaną jest wartość współdzielona przekazanego obiektu typu Delay
lub podana jako argument wartość, jeśli nie mamy do czynienia z Delay’em.
Testowanie Delay’ów
Testowanie typu, delay?
Funkcja delay?
służy do sprawdzania, czy podana wartość jest obiektem typu
Delay
.
Użycie:
(delay? wartość)
.
Pierwszym, obowiązkowym argumentem funkcji powinna być wartość, której typ zostanie sprawdzony.
Funkcja zwraca wartość true
, jeżeli podana wartość jest obiektem typu
Delay
, a false
w przeciwnym razie.
Sprawdzanie uzyskania wyniku, realized?
Funkcja realized?
służy do sprawdzania, czy realizowany w osobnym wątku
podprogram przeprowadzający obliczenia, które służą do uzyskania wartości
bieżącej Delay’a, zakończył się pomyślnie i ustawiono wartość bieżącą
referencji.
Użycie:
(realized? delay)
.
Pierwszym, obowiązkowym argumentem funkcji powinien być obiekt typu Delay
.
Funkcja zwraca wartość true
, jeżeli ustawiono wartość bieżącą, a false
w przeciwnym razie.
Volatile’e
Volatile to mechanizm podobny do Atomów, jednak pozbawiony obsługi funkcji służących do badania poprawności wartości bieżących (walidatorów) i nie wyposażony w możliwość rejestrowania tzw. funkcji obserwujących. Jest to sposób utrzymywania lokalnego stanu zbliżony do zmiennych znanych z innych języków programowania, a wykorzystywany głównie w tzw. transduktorach.
Z uwagi na brak gwarancji, że operacje będą atomowe, nie powinno się używać Volatile’i do zarządzania współdzielonymi stanami, ale do szybkiej wymiany informacji w obrębie pojedynczego, izolowanego wątku z opcjonalnymi odczytami wartości współdzielonych w innych wątkach.
Volatile
jest typem referencyjnym i przechowuje odniesienia do
wartości, które mogą być zmieniane z użyciem odpowiednich funkcji.
Operacje zmian powiązań, jak również ich odczytów, korzystają z obiektów
volatile
Javy, które cechuje to, że ani procesor, ani kompilator JIT, nie
zmienią kolejności ich realizowania. Wykorzystywane są m.in. pamięci podręczne
i inne mechanizmy przyspieszające dostęp do zawartości RAM-u. Z tej właśnie
przyczyny aktualizacje Volatile’i nie są atomowe.
Technicznie rzecz ujmując, obiekty typu Volatile
zachowują się podobnie do
zmiennych lokalnych, z tą różnicą, że izolowanie w wątku
nie jest wymuszane. Jeżeli programista nie będzie ostrożny, może nadpisać
współdzieloną wartość bieżącą i zaburzyć pracę innego wątku, który operuje na
danym Volatile’u.
Gdy obiekt typu Volatile
zmienia wartość bieżącą, dochodzi do podmiany
(ang. swap) wartości, na którą wskazuje. Nowa wartość będzie zwykle bazowała
na poprzedniej (będzie efektem zastosowania na niej jakiejś funkcji), chociaż
można też powiązać obiekt referencyjny z podaną wartością stałą.
Korzystanie z Volatile’i polega na utworzeniu odpowiedniego obiektu,
a następnie aktualizowaniu jego powiązań z wartościami. Zazwyczaj obiekt
typu Volatile
będzie dodatkowo w sposób stały utożsamiany z symbolem
określonym zmienną globalną lub
powiązaniem leksykalnym. W ten sposób możliwym stanie
się posługiwanie się nim z użyciem nazwy widocznej w odpowiednich obszarach
programu.
Tworzenie
Tworzenie obiektów typu Volatile
, volatile!
Do tworzenia obiektów typu Volatile
służy funkcja volatile!
.
Użycie:
(volatile! wartość-początkowa)
.
Funkcja przyjmuje jeden obowiązkowy argument – wartość początkową, która będzie
wskazywana przez referencję – a zwraca obiekt typu Volatile
.
Wyrażenie z linii nr 1 tworzy obiekt referencyjny typu Volatile
, który odnosi
się do wartości 0.
Ostatnie wyrażenie przykładu ustawia wartość początkową na wektor stworzony z użyciem wektorowego S-wyrażenia.
Identyfikowanie Volatile’i
Volatile’e są obiektami, z którymi, podobnie jak z innymi pamięciowymi
wartościami, można powiązać symboliczne identyfikatory i w ten sposób
określać ich tożsamości. Możemy więc stwarzać odnoszące się do obiektów typu
Volatile
globalne zmienne, jak również korzystać z innych
rodzajów powiązań (np. leksykalnych).
Pobieranie wartości
Odczytywanie wartości Volatileów, deref
Żeby odczytać wartość Volatileu możemy użyć funkcji deref
.
Użycie:
(deref volatile)
.
Funkcja przyjmuje jeden argument, którym powinien być obiekt typu Volatile
,
a zwraca wartość, do której odniesienie jest przechowywane w tym obiekcie.
Dereferencja Volatileów, makro @
Makro czytnika @
(znak małpki) umieszczone przed wyrażeniem sprawia, że jeśli
zwracany przez nie obiekt jest typem referencyjnym, to wywołana zostanie na nim
funkcja odpowiedzialna za odczyt wskazywanej wartości. W przypadku Volatile’i
będzie to omówiona wyżej funkcja deref
.
Użycie:
@volatile
.
Zmiany wartości
Aktualizowanie wartości, vswap!
Zmiana stanu obiektu typu Volatile
, czyli aktualizacja odniesienia, aby
wskazywało inną wartość, możliwa jest z zastosowaniem funkcji vswap!
.
Użycie:
(vswap! volatile funkcja & argument…)
.
Pierwszy przyjmowany argument powinien być symbolem identyfikującym obiekt typu
Volatile
lub inne wyrażenie, które przeliczone zwróci ten obiekt. Drugim
argumentem powinna być funkcja, która jako pierwszy argument przyjmie wartość
bieżącą, do której odnosi się Volatile i na jej podstawie obliczy i zwróci nową
wartość. Zostanie ona użyta do zastąpienia poprzedniego referencyjnego
odniesienia w Volatile’u. Opcjonalnie możemy podać dodatkowe argumenty, które
zostaną przekazane jako kolejne argumenty wywoływanej funkcji.
Wartością zwracaną przez funkcję vswap!
jest nowa wartość bieżąca referencji.
Uwaga: Podmiana wartości nie będzie atomowa! Może zdarzyć się, że dwa wątki operujące na tym samym obiekcie typu
Volatile
zaktualizują jego wartość bieżącą, lecz później uruchomiony użyje wartości, która została odczytana zanim drugi wątek ją zmienił.
Ustawianie wartości, vreset!
Funkcja vreset!
pozwala podmienić obiekt, do którego odnosi się Volatile, ale
bez obliczania nowej wartości na bazie aktualnie powiązanej z Volatile’em.
Użycie:
(vreset! volatile wartość)
.
Funkcja przyjmuje dwa argumenty: obiekt typu Volatile
i nową wartość.
Wartością zwracaną jest nowa wartość bieżąca referencji.