Dlaczego Lotny nie jest uważany za przydatny w programowaniu wielowątkowym C lub c++?

Jak pokazano w ta odpowiedź, którą ostatnio opublikowałem, wydaje mi się, że jestem zdezorientowany co do użyteczności (lub jej braku) volatile w wielowątkowych kontekstach programowania.

Rozumiem, że za każdym razem, gdy zmienna może zostać zmieniona poza przepływem sterowania kawałkiem kodu, który do niej dostępuje, zmienna powinna być zadeklarowana jako volatile. Takie sytuacje stanowią manipulatory sygnału, rejestry I/o oraz zmienne zmodyfikowane przez inny wątek.

Więc, jeśli masz globalny int foo i {[2] } jest odczytywany przez jeden wątek i ustawiany atomicznie przez inny wątek (prawdopodobnie za pomocą odpowiedniej instrukcji maszynowej), wątek czytający widzi tę sytuację w taki sam sposób, w jaki widzi zmienną zmienioną przez obsługę sygnału lub zmodyfikowaną przez zewnętrzny stan sprzętowy i dlatego foo powinna być zadeklarowana volatile (lub, w przypadku sytuacji wielowątkowych, dostęp z obciążeniem zabezpieczonym pamięcią, co jest prawdopodobnie lepszym rozwiązaniem).

Jak i gdzie się mylę?
Author: Community, 2010-03-21

9 answers

Problem z volatile w kontekście wielowątkowym polega na tym, że nie zapewnia wszystkich gwarancji, których potrzebujemy. Ma kilka właściwości, których potrzebujemy, ale nie wszystkie, więc nie możemy polegać na volatile alone .

Jednak prymitywy, których będziemy musieli użyć dla pozostałych właściwości , dostarczają również tych, które volatile robią, więc jest to w praktyce niepotrzebne.

Dla bezpiecznego dostępu do udostępnianych danych, potrzebujemy gwarancji że:

  • Odczyt/Zapis rzeczywiście się dzieje (kompilator nie tylko przechowuje wartość w rejestrze zamiast tego i odkłada aktualizację pamięci głównej na znacznie później)
  • że zmiana kolejności nie ma miejsca. Załóżmy, że używamy zmiennej volatile jako znacznika, aby wskazać, czy niektóre dane są gotowe do odczytu. W naszym kodzie po prostu ustawiamy flagę po przygotowaniu danych, więc wszystko wygląda dobrze. Ale co jeśli instrukcje są zmieniane tak flaga jest ustawiona pierwszy ?

volatile gwarantuje pierwszy punkt. Gwarantuje również, że nie nastąpi zmiana kolejności pomiędzy różnymi zmiennymi odczytami/zapisami . Wszystkie operacje dostępu do pamięci volatile będą wykonywane w określonej kolejności. To wszystko, czego potrzebujemy do tego, do czego jest przeznaczona volatile: manipulowanie rejestrami We/Wy lub sprzętem mapowanym w pamięci, ale nie pomaga nam to w wielowątkowym kodzie, gdzie obiekt volatile jest często używany tylko do synchronizacji dostępu do nieulotnych danych. Te dostępy mogą być zmieniane względem volatile.

Rozwiązaniem zapobiegającym zmianie kolejności jest użycie bariery Pamięci , która wskazuje zarówno kompilatorowi, jak i procesorowi, że żaden dostęp do pamięci nie może być zmieniony w tym punkcie. Umieszczenie takich barier wokół naszego zmiennego dostępu lotnego zapewnia, że nawet nieulotne dostępy nie będą zmieniane na Lotny, co pozwala nam pisać bezpieczny dla wątku kod.

[[12]}jednak bariery pamięci również upewnij się, że wszystkie oczekujące odczyty / zapisy są wykonywane po osiągnięciu bariery, więc skutecznie daje nam to wszystko, czego potrzebujemy, czyniąc volatile niepotrzebnym. Możemy po prostu całkowicie usunąć volatile kwalifikator.

Od C++11 zmienne atomowe (std::atomic<T>) dają nam wszystkie odpowiednie gwarancje.

 193
Author: jalf,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-17 20:18:22

Możesz to również rozważyć z dokumentacji jądra Linuksa .

Programiści C często brali lotne, aby oznaczać, że zmienna może być zmieniony poza bieżącym wątkiem wykonania; jako wynik, są czasami kuszeni, aby użyć go w kodzie jądra, gdy wykorzystywane są wspólne struktury danych. Innymi słowy, zostały wiadomo, że traktuje lotne typy jako rodzaj łatwej zmiennej atomowej, która nie są. Zastosowanie lotnych w jądrze kod jest prawie nigdy poprawne; dokument ten opisuje dlaczego.

Kluczowym punktem do zrozumienia w odniesieniu do lotnych jest to, że jego celem jest powstrzymanie optymalizacji, która prawie nigdy nie jest tym, co naprawdę chce to zrobić. W jądrze należy chronić współdzielone dane struktur przed niechcianym dostępem współbieżnym, co jest bardzo inne zadanie. Proces ochrony przed niechcianymi współbieżność pozwoli również uniknąć prawie wszystkich problemów związanych z optymalizacją in a more skuteczny sposób.

Podobnie jak lotne, jądro prymitywne, które umożliwiają współbieżny dostęp do bezpieczeństwo danych (spinlocki, muteksy, bariery pamięci itp.) są przeznaczone do zapobiegaj niepożądanej optymalizacji. Jeśli są one stosowane prawidłowo, tam nie będzie też potrzeby używania lotnych. Jeśli Lotny jest nadal konieczne, jest prawie na pewno błąd w kodzie gdzieś. W poprawnie napisany kod jądra, może służyć jedynie spowolnieniu na ziemię.

Rozważmy typowy blok kodu jądra:

spin_lock(&the_lock);
do_something_on(&shared_data);
do_something_else_with(&shared_data);
spin_unlock(&the_lock);

Jeśli cały kod jest zgodny z regułami blokowania, wartość shared_data nie można zmienić nieoczekiwanie, gdy blokada jest utrzymywana. Dowolny inny kod które mogą chcieć zagrać z tymi danymi będą czekać na blokadę. Pierwotniaki spinlocka działają jako bariery pamięci - są wyraźnie napisane w tym celu-co oznacza, że dostęp do danych nie będzie zoptymalizowany przez nie. Więc kompilator może pomyśleć, że wie, co będzie w shared_data, ale spin_lock() wywołania, ponieważ działa jak pamięć bariera, zmusi go do zapomnienia wszystkiego, co wie. Nie będzie problemy z optymalizacją dostępu do tych danych.

Jeśli shared_data zostanie zadeklarowana jako zmienna, blokada nadal będzie konieczne. Ale kompilator również nie będzie mógł optymalizować dostęp do shared_data wewnątrz sekcji krytycznej, gdy wiemy, że nikt inny nie może z nim pracować. Podczas trzymania zamka, shared_data nie jest zmienna. Podczas transakcji z udostępnionymi danymi, właściwe blokowanie sprawia, że lotność jest niepotrzebna - i potencjalnie szkodliwa.

Klasa volatile storage była pierwotnie przeznaczona dla mapowanych w pamięci We/Wy rejestry. W jądrze również dostęp do rejestru powinien być zabezpieczony zamkami, ale też nie chce się kompilatora "optymalizacja" dostępu do rejestru w sekcji krytycznej. Ale, wewnątrz jądra, dostęp do pamięci We/Wy jest zawsze dokonywany przez accesor funkcji; dostęp do pamięci We / Wy bezpośrednio poprzez / align = "left" / na i nie działa na wszystkich architekturach. Te akcesoria to napisany, aby zapobiec niepożądanej optymalizacji, więc po raz kolejny Lotny jest niepotrzebnie.

Inną sytuacją, w której można pokusić się o użycie lotnych procesor jest zajęty-oczekuje na wartość zmiennej. Prawo sposób wykonania zajętego oczekiwania to:

while (my_variable != what_i_want)
    cpu_relax();

Wywołanie cpu_relax () może obniżyć zużycie energii procesora lub uzyskać hyperthreaded twin processor; to także tak się składa, że służy jako wspomnienie bariera, więc po raz kolejny lotność jest niepotrzebna. Oczywiście., zapracowane oczekiwanie jest na ogół aktem antyspołecznym.

Jest jeszcze kilka rzadkich sytuacji, w których lotność ma sens w jądro:

  • Wyżej wymienione funkcje accessora mogą używać lotnych na architektury, w których działa bezpośredni dostęp do pamięci We/Wy. Zasadniczo, każde wywołanie accessor staje się samodzielną małą sekcją krytyczną i zapewnia, że dostęp odbywa się zgodnie z oczekiwaniami programisty.

  • Wbudowany kod montażowy, który zmienia pamięć, ale który nie ma innego widoczne skutki uboczne, ryzyko usunięcia przez GCC. Dodawanie lotnych słowa kluczowe do instrukcji asm zapobiegną temu usunięciu.

  • Zmienna jiffies jest wyjątkowa, ponieważ może mieć inną wartość za każdym razem, gdy jest odwołany, ale można go odczytać bez żadnych specjalnych zamykam. Więc jiffies mogą być lotne, ale dodanie innych zmienne tego typu są bardzo źle postrzegane. Jiffies jest uważany być" głupim dziedzictwem " (słowa Linusa) w tym względzie; naprawianie go byłoby więcej kłopotów niż warto.

  • Wskaźniki do struktur danych w pamięci koherentnej, które mogą być modyfikowane przez urządzenia We/Wy mogą czasami być lotne. Bufor pierścieniowy używany przez kartę sieciową, gdzie ta karta zmienia Wskaźniki na wskazać, które deskryptory zostały przetworzone, jest przykładem tego rodzaj sytuacji.

W przypadku większości kodów nie ma zastosowania żadne z powyższych uzasadnień dotyczących lotności. W rezultacie użycie lotnych może być postrzegane jako błąd i wprowadzi dodatkową kontrolę do Kodeksu. Deweloperzy, którzy są skusić się na użycie lotnych należy zrobić krok w tył i pomyśleć o tym, co oni naprawdę starają się osiągnąć.

 44
Author: Muhammed Abiola,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-04-26 03:42:44

Nie sądzę, że się mylisz -- volatile jest konieczne, aby zagwarantować, że wątek A zobaczy zmianę wartości, jeśli wartość zostanie zmieniona przez coś innego niż wątek A. Jak rozumiem, volatile jest w zasadzie sposobem na powiedzenie kompilatorowi "nie buforuj tej zmiennej w rejestrze, zamiast tego pamiętaj, aby zawsze czytać/zapisywać ją z pamięci RAM przy każdym dostępie".

Zamieszanie jest spowodowane tym, że lotność nie wystarcza do implementacji wielu rzeczy. W szczególności nowoczesne systemy wykorzystują wiele poziomów buforowania, nowoczesne wielordzeniowe procesory wykonują fantazyjne optymalizacje w czasie uruchamiania, a nowoczesne Kompilatory wykonują fantazyjne optymalizacje w czasie kompilacji, a wszystko to może skutkować różnymi efektami ubocznymi pojawiającymi się w innej kolejności niż oczekiwano, gdybyś spojrzał na kod źródłowy.

Więc lotne jest w porządku, o ile pamiętaj, że "obserwowane" zmiany w zmiennej lotnej mogą nie wystąpić dokładnie w czasie, o którym myślisz,że będą. W szczególności, nie spróbuj użyć zmiennych lotnych jako sposobu synchronizacji lub porządkowania operacji między wątkami, ponieważ nie będzie to działać niezawodnie.

Osobiście, mój główny (tylko?) flagę lotną używa się jako boolean "pleaseGoAwayNow". Jeśli mam wątek roboczy, który zapętla się w sposób ciągły, będę musiał sprawdzić lotną wartość logiczną na każdej iteracji pętli i zakończyć, jeśli wartość logiczna jest kiedykolwiek prawdziwa. Główny wątek może następnie bezpiecznie wyczyścić wątek roboczy, ustawiając wartość logiczną na true, a następnie wywołując pthread_join (), aby poczekać, aż wątek roboczy zniknie.

 10
Author: Jeremy Friesner,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-20 22:19:57

Twoje zrozumienie naprawdę jest złe.

Właściwość, którą posiadają zmienne lotne, to "odczyt i zapis do tej zmiennej są częścią postrzegalnego zachowania programu". Oznacza to, że ten program działa (biorąc pod uwagę odpowiedni sprzęt):

int volatile* reg=IO_MAPPED_REGISTER_ADDRESS;
*reg=1; // turn the fuel on
*reg=2; // ignition
*reg=3; // release
int x=*reg; // fire missiles

Problem w tym, że to nie jest właściwość, której chcemy od niczego zabezpieczającego wątek.

Na przykład licznik bezpieczny dla wątku byłby po prostu (kod podobny do jądra Linuksa, nie znam odpowiednika C++0x):

atomic_t counter;

...
atomic_inc(&counter);

To jest atomowy, bez bariery pamięci. W razie potrzeby należy je dodać. Dodanie volatile prawdopodobnie nie pomogłoby, ponieważ nie wiązałoby się z dostępem do pobliskiego kodu (np. do dodania elementu do listy licznik jest liczony). Z pewnością nie musisz widzieć licznika inkrementowanego poza twoim programem, a optymalizacje są nadal pożądane, np.

atomic_inc(&counter);
atomic_inc(&counter);

Można jeszcze zoptymalizować do

atomically {
  counter+=2;
}

Jeśli optymalizator jest wystarczająco inteligentny (nie zmienia semantyki kod).

 6
Author: jpalecek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-20 22:43:18

volatile jest przydatny (choć niewystarczający)do implementacji podstawowej konstrukcji spinlocka mutex, ale gdy już go posiadasz (lub coś lepszego), nie potrzebujesz innego volatile.

Typowym sposobem programowania wielowątkowego nie jest ochrona każdej współdzielonej zmiennej na poziomie maszyny, ale raczej wprowadzenie zmiennych ochronnych, które kierują przepływem programu. Zamiast volatile bool my_shared_flag; powinieneś mieć

pthread_mutex_t flag_guard_mutex; // contains something volatile
bool my_shared_flag;

To nie tylko zawiera "trudną część", ale jest zasadniczo konieczne: C nie obejmuje operacji atomowych niezbędnych do wdrożenia mutex; ma tylko volatile, aby zapewnić dodatkowe gwarancje o zwykłych operacjach .

Teraz masz coś takiego:

pthread_mutex_lock( &flag_guard_mutex );
my_local_state = my_shared_flag; // critical section
pthread_mutex_unlock( &flag_guard_mutex );

pthread_mutex_lock( &flag_guard_mutex ); // may alter my_shared_flag
my_shared_flag = ! my_shared_flag; // critical section
pthread_mutex_unlock( &flag_guard_mutex );

my_shared_flag nie musi być Lotny, mimo że jest nie do przerobienia, ponieważ

  1. inny wątek ma do niego dostęp.
  2. co oznacza, że odniesienie do niego musiało zostać zrobione kiedyś (z operatorem &).
    • (lub odniesienie do zawierające strukturę)
  3. {[8] } jest funkcją biblioteczną.
  4. co oznacza, że kompilator nie może stwierdzić, czy pthread_mutex_lock w jakiś sposób zdobywa to odniesienie.
  5. co oznacza, że kompilator musi założyć , że pthread_mutex_lock modyfikuje wspólną flagę !
  6. więc zmienna musi być przeładowana z pamięci. volatile, choć znaczące w tym kontekście, jest obce.
 6
Author: Potatoswatter,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-20 23:23:21

Aby dane były spójne w środowisku równoległym, należy zastosować dwa warunki:

1) Atomiczność tzn. jeśli odczytam lub zapiszę jakieś dane do pamięci, to dane te będą odczytywane/zapisywane w jednym przejściu i nie mogą zostać przerwane lub zakwestionowane z powodu np. przełącznika kontekstowego

2) spójność tzn. kolejność operacji odczytu/zapisu musi być widziana, aby była taka sama między wieloma współbieżnymi środowiskami-czy to wątki, maszyny itp

Lotna nie pasuje do żadnego z powyżej-a dokładniej, standard c lub c++ dotyczący zachowania lotności nie zawiera żadnego z powyższych.

Jest to jeszcze gorsze w praktyce, ponieważ niektóre Kompilatory (takie jak kompilator intel Itanium) próbują zaimplementować pewien element bezpiecznego dostępu współbieżnego (tj. poprzez zapewnienie ogrodzeń pamięci), jednak nie ma spójności między implementacjami kompilatorów, a ponadto standard nie wymaga tego od implementacji w pierwszej kolejności.

Oznaczenie a zmienna jako lotna będzie po prostu oznaczać, że zmuszasz wartość do przepłukiwania do i z pamięci za każdym razem, co w wielu przypadkach po prostu spowalnia Kod, ponieważ w zasadzie zepsułeś wydajność pamięci podręcznej.

C # i java AFAIK robią to poprzez wprowadzenie 1) i 2) jednak tego samego nie można powiedzieć o kompilatorach c/C++, więc w zasadzie rób z tym, co uważasz za stosowne.

Aby uzyskać bardziej dogłębną (choć nie bezstronną ) dyskusję na ten temat przeczytaj to

 6
Author: zebrabox,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-03-21 01:28:08

Komp.programowanie.FAQ wątków zawiera Klasyczne Wyjaśnienie autorstwa Dave ' a Butenhofa:

Q56: dlaczego nie muszę deklarować współdzielonych zmiennych?

Martwię się jednak o przypadki, w których zarówno kompilator jak i biblioteka wątków spełnia ich odpowiednie specyfikacje. / Align = "left" / Kompilator C może globalnie przydzielić pewną współdzieloną (nieulotną) zmienną do rejestr, który zostaje zapisany i przywrócony, gdy procesor przechodzi z nitka do nitki. Każdy wątek będzie miał swoją prywatną wartość dla tej współdzielonej zmiennej, która nie jest tym, czego chcemy od współdzielonej zmienna.

W pewnym sensie jest to prawdą, jeśli kompilator wie wystarczająco dużo o odpowiednie zakresy zmiennej i pthread_cond_wait (lub pthread_mutex_lock) funkcji. W praktyce większość kompilatorów nie spróbuje do przechowywania kopii rejestrowych danych globalnych w trakcie połączenia z zewnętrznym funkcji, bo zbyt trudno stwierdzić, czy rutyna może jakoś mieć dostęp do adresu danych.

Więc tak, to prawda, że kompilator zgodny ściśle (ale bardzo agresywnie) do ANSI C może nie działać z wieloma wątkami bez Lotny. Ale lepiej niech ktoś to naprawi. Ponieważ każdy SYSTEM (czyli, pragmatycznie, połączenie jądra, bibliotek i kompilatora C), które nie zapewnia gwarancji spójności pamięci POSIX nie odpowiada do standardu POSIX. Kropka. System nie może wymagać użycia lotne na współdzielonych zmiennych dla poprawnego zachowania, ponieważ POSIX wymaga tylko, aby funkcje synchronizacji POSIX były niezbędne.

Więc jeśli twój program się zepsuje, ponieważ nie używałeś volatile, to jest to błąd. Może to nie być błąd w C, błąd w bibliotece wątków lub błąd w jądro. Ale to błąd systemowy, a co najmniej jeden z tych komponentów będzie musiał pracować, aby to naprawić.

Nie chcesz używać volatile, bo na każdym systemie, w którym robi każda różnica, to będzie znacznie droższy od właściwego zmienna nieulotna. (ANSI C wymaga "punktów sekwencji" dla lotnych zmiennych przy każdym wyrażeniu, podczas gdy POSIX wymaga ich tylko przy operacje synchronizacyjne - wielowątkowa aplikacja o dużej wydajności obliczeniowej zauważy znacznie większą aktywność pamięci przy użyciu lotnych, a po wszystko, to aktywność pamięci naprawdę cię spowalnia.)

/---[ Dave Butenhof ]-----------------------[ [email protected] ] - - - \
/ Cyfrowe Equipment Corporation 110 Spit Brook Rd ZKO2-3/Q18 |
|603.881.2218, FAX 603.881.0120]} ----------------- [Lepsze Życie Dzięki Współbieżności ]----------------/

Mr Butenhof pokrywa większość tego samego gruntu w ten usenetowy post :

Użycie "lotnych" nie jest wystarczające do zapewnienia właściwej pamięci widoczność lub synchronizacja między wątkami. Zastosowanie mutex jest wystarczające, oraz, z wyjątkiem uciekając się do różnych przenośnych maszyn alternatywy kodu (lub bardziej subtelne implikacje pamięci POSIX zasady, które są znacznie trudniejsze do zastosowania ogólnie, jak wyjaśniono w mój poprzedni post), mutex jest niezbędny.

Dlatego, jak wyjaśnił Bryan, użycie lotnych nic poza tym, aby uniemożliwić kompilatorowi uczynienie użytecznym i pożądanym optymalizacji, nie pomagając w tworzeniu kodu " wątku Bezpieczny". Zapraszamy, oczywiście, do zadeklarowania wszystko co chcesz jako "Lotny" - w końcu to legalny atrybut ANSI C. Just nie oczekuj, że rozwiąże to problemy z synchronizacją wątków.

Wszystko co dotyczy C++.

 5
Author: Tony Delroy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-08-15 16:22:06

Zgodnie z moim starym standardem C, "to, co stanowi dostęp do obiektu, który ma zmienny typ, jest zdefiniowane w implementacji". Tak więc autorzy kompilatorów C mogli wybrać "volatile" mean "thread safe access in a multi-process environment" . Ale tego nie zrobili.

Zamiast tego, operacje wymagane do zapewnienia bezpieczeństwa wątku sekcji krytycznej w wielordzeniowym wieloprocesowym środowisku pamięci współdzielonej zostały dodane jako nowa implementacja zdefiniowana funkcje. I, uwolniony od wymogu, że "lotna" zapewniałaby dostęp atomowy i porządkowanie dostępu w środowisku wieloprocesowym, pisarze kompilatorów ustalali priorytet redukcji kodu nad historyczną semantyką "lotną" zależną od implementacji.

Oznacza to, że takie rzeczy jak" lotne " semafory wokół krytycznych sekcji kodu, które nie działają na nowym sprzęcie z nowymi kompilatorami, mogły kiedyś działać ze starymi kompilatorami na starym sprzęcie, a stare przykłady czasami nie są złe, po prostu Stary.

 3
Author: david,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-11-14 11:34:30

To wszystko, co robi "Lotny" : "Hej kompilatorze, ta zmienna może się zmienić w dowolnym momencie (na dowolnym tyknięciu zegara), nawet jeśli nie działają na niej lokalne instrukcje. Nie buforuj tej wartości w rejestrze."

To jest to. Mówi kompilatorowi, że twoja wartość jest, cóż, zmienna - wartość ta może zostać zmieniona w dowolnym momencie przez zewnętrzną logikę (inny wątek, inny proces, jądro itp.). Istnieje mniej więcej wyłącznie po to, aby tłumić optymalizacje kompilatorów, które będą cicho buforować wartość w rejestrze, że jest z natury niebezpieczne, aby kiedykolwiek buforować.

Możesz napotkać artykuły takie jak "Dr Dobbs", które rzucają się w oczy jako panaceum na programowanie wielowątkowe. Jego podejście nie jest całkowicie pozbawione wartości, ale ma podstawową wadę polegającą na tym, że użytkownicy obiektu są odpowiedzialni za jego bezpieczeństwo wątku, co zwykle ma te same problemy, co inne naruszenia enkapsulacji.

 2
Author: Zack Yezek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-08-02 01:54:26