Co to jest stan rasy?

Podczas pisania aplikacji wielowątkowych jednym z najczęstszych problemów są warunki rasowe.

Moje pytania do społeczności to:

Jaki jest stan rasy? Jak je wykrywasz? Jak sobie z nimi radzisz? Wreszcie, jak zapobiec ich wystąpieniu?

Author: TylerH, 2008-08-29

17 answers

Warunek rasy występuje, gdy dwa lub więcej wątków może uzyskać dostęp do udostępnionych danych i próbują je zmienić w tym samym czasie. Ponieważ algorytm planowania wątków może w dowolnym momencie przełączać się między wątkami, nie wiesz, w jakiej kolejności wątki będą próbowały uzyskać dostęp do udostępnionych danych. Dlatego wynik zmiany danych zależy od algorytmu harmonogramowania wątków, tzn. oba wątki "ścigają się", aby uzyskać dostęp/zmienić dane.

Problemy często występują, gdy jeden wątek robi "check-then-act" (np. "check" jeśli wartością jest X, to "act", aby zrobić coś, co zależy od wartości X) , a inny wątek robi coś z wartością pomiędzy "check" I "act". Np.:

if (x == 5) // The "Check"
{
   y = x * 2; // The "Act"

   // If another thread changed x in between "if (x == 5)" and "y = x * 2" above,
   // y will not be equal to 10.
}

Chodzi o to, że y może być 10, lub może być cokolwiek, w zależności od tego, czy inny wątek zmienił x pomiędzy czekiem a aktem. Nie możesz tego wiedzieć.

Aby zapobiec występowaniu warunków wyścigowych, zazwyczaj umieszczasz blokadę wokół współdzielonego dane, aby zapewnić tylko jeden wątek może uzyskać dostęp do danych na raz. To by oznaczało coś takiego:

// Obtain lock for x
if (x == 5)
{
   y = x * 2; // Now, nothing can change x until the lock is released. 
              // Therefore y = 10
}
// release lock for x
 950
Author: Lehane,
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-07 11:03:02

"race condition" istnieje, gdy wielowątkowy (lub w inny sposób równoległy) kod, który uzyskałby dostęp do współdzielonego zasobu, mógłby to zrobić w taki sposób, aby spowodować nieoczekiwane wyniki.

Weźmy ten przykład:

for ( int i = 0; i < 10000000; i++ )
{
   x = x + 1; 
}

Gdybyś miał 5 wątków wykonujących ten kod jednocześnie, wartość x nie skończyłaby się na 50 000 000. W rzeczywistości różniłoby się to w każdym biegu.

Dzieje się tak dlatego, że aby każdy wątek mógł zwiększyć wartość x, muszą wykonać następujące czynności: (uproszczone, oczywiście)

Retrieve the value of x
Add 1 to this value
Store this value to x

Każdy wątek może być na każdym etapie tego procesu w dowolnym momencie, i mogą one wejść na siebie, gdy współdzielony zasób jest zaangażowany. Stan x może zostać zmieniony przez inny wątek w czasie pomiędzy odczytem x i jego zapisem z powrotem.

Powiedzmy, że wątek pobiera wartość x, ale jeszcze jej nie zapisał. Inny wątek może również pobrać tę samą wartość X (ponieważ żaden wątek jeszcze jej nie zmienił) i wtedy oba będą przechowywać ta sama wartość (x+1) z powrotem w x!

Przykład:

Thread 1: reads x, value is 7
Thread 1: add 1 to x, value is now 8
Thread 2: reads x, value is 7
Thread 1: stores 8 in x
Thread 2: adds 1 to x, value is now 8
Thread 2: stores 8 in x

Warunki rasowe można uniknąć poprzez zastosowanie pewnego rodzaju mechanizmu blokującego przed kodem dostępującym do udostępnionego zasobu:

for ( int i = 0; i < 10000000; i++ )
{
   //lock x
   x = x + 1; 
   //unlock x
}
Tutaj odpowiedź wychodzi za każdym razem jako 50,000,000.

Aby dowiedzieć się więcej o blokowaniu, Szukaj: mutex, SEMAFOR, sekcja krytyczna, współdzielony zasób.

 180
Author: privatehuff,
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-11-12 19:31:01

Jaki jest stan rasy?

Planujesz iść do kina o 17: 00. O dostępność biletów pytają Państwo o godzinie 16.00. Przedstawiciel mówi, że są one dostępne. Zrelaksujesz się i dotrzesz do kasy biletowej 5 minut przed pokazem. Jestem pewien, że zgadniesz, co się stanie: to ful. Problem tkwił w czasie między czekiem a akcją. Zapytałeś o 4 i działałeś o 5. W międzyczasie ktoś inny zabrał bilety. To jest stan wyścigu-w szczególności scenariusz "check-then-act" warunków wyścigu.
Jak je wykryć?

Przegląd kodu religijnego, wielowątkowe testy jednostkowe. Nie ma skrótu. Jest kilka wtyczek Eclipse pojawiających się na tym, ale nic stabilnego jeszcze.

Jak sobie z nimi radzić i im zapobiegać?

Najlepszą rzeczą byłoby tworzenie efektów ubocznych funkcji wolnych i bezpaństwowych, używanie immutables w jak największym stopniu. Ale nie zawsze jest to możliwe. Więc używając Javy.util./ align = "left" / atomic, współbieżne struktury danych, odpowiednia synchronizacja i współbieżność oparta na actor pomogą.

Najlepszym zasobem dla współbieżności jest JCIP. Możesz również uzyskać więcej szczegółów na temat powyższego wyjaśnienia tutaj .

 114
Author: Vishal Shukla,
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-08-05 06:11:31

Istnieje istotna różnica techniczna między Warunkami wyścigu i wyścigów danych. Większość odpowiedzi wydaje się zakładać, że te terminy są równoważne, ale tak nie jest.

Wyścig danych występuje, gdy 2 instrukcje uzyskują dostęp do tego samego miejsca pamięci, co najmniej jeden z tych dostępów jest zapisem i nie ma dzieje się przed zamówieniem wśród tych dostępów. Teraz, co stanowi dzieje się przed zamówieniem, jest przedmiotem wielu dyskusji, ale ogólnie ulock-lock pary na ta sama zmienna blokady i pary wait-signal na tej samej zmiennej warunkowej wywołują happens-before order.

Warunek rasy jest błędem semantycznym. Jest to wada występująca w czasie lub kolejności zdarzeń, która prowadzi do błędnego zachowania programu .

Wiele warunków wyścigowych może być (i w rzeczywistości są) spowodowane przez wyścigi danych, ale nie jest to konieczne. W rzeczywistości wyścigi danych i warunki wyścigowe nie są ani koniecznym, ani wystarczającym warunkiem dla jednego kolejny. ten post na blogu wyjaśnia również różnicę bardzo dobrze, na prostym przykładzie transakcji bankowych. Oto kolejny prosty przykład , który wyjaśnia różnicę.

Teraz, gdy przybiliśmy terminologię, spróbujmy odpowiedzieć na pierwotne pytanie.

Biorąc pod uwagę, że Warunki rasy są błędami semantycznymi, nie ma ogólnego sposobu na ich wykrycie. Dzieje się tak dlatego, że nie ma możliwości posiadania zautomatyzowanej oracle, która może odróżnić poprawny od nieprawidłowego programu zachowanie w ogólnym przypadku. Wykrywanie rasy jest problemem nie do podjęcia decyzji.

Z drugiej strony, wyścigi danych mają precyzyjną definicję, która niekoniecznie musi odnosić się do poprawności, a zatem można je wykryć. Istnieje wiele smaków detektorów danych wyścigowych (statyczne/dynamiczne wykrywanie danych wyścigowych, wykrywanie danych na bazie lockset, wykrywanie danych happens-before, hybrydowe wykrywanie danych wyścigowych). Najnowocześniejszym detektorem dynamicznego wyścigu danych jest ThreadSanitizer , który bardzo dobrze sprawdza się w praktyce.

Obsługa wyścigów danych w ogóle wymaga pewnej dyscypliny programistycznej, aby wywoływać zdarzenia-przed krawędziami między dostępami do współdzielonych danych (albo podczas programowania, albo po ich wykryciu za pomocą wyżej wymienionych narzędzi). można to zrobić poprzez blokady, zmienne warunkowe, semafory, itd. Jednak można również stosować różne paradygmaty programowania, takie jak przekazywanie wiadomości (zamiast pamięci współdzielonej), które unikają wyścigów danych przez konstrukcję.

 53
Author: Baris Kasikci,
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
2017-05-23 10:31:37

Tak jakby kanoniczną definicją jest " , gdy dwa wątki mają dostęp do tego samego miejsca w pamięci w tym samym czasie, a przynajmniej jeden dostęp jest zapisem ."W tej sytuacji wątek" czytelnik "może otrzymać starą lub nową wartość, w zależności od tego, który wątek" wygra wyścig."Nie zawsze jest to błąd-w rzeczywistości niektóre bardzo owłosione algorytmy niskiego poziomu robią to celowo-ale generalnie należy go unikać. @ Steve Gury podaj dobry przykład kiedy może być problem.

 32
Author: Chris Conway,
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
2008-08-29 16:21:41

Stan rasy jest rodzajem robaka, który występuje tylko w pewnych warunkach czasowych.

Przykład: Wyobraź sobie, że masz dwa wątki, A I B.

W Wątku A:

if( object.a != 0 )
    object.avg = total / object.a

W Wątku B:

object.a = 0

Jeśli wątek A jest poprzedzony zaraz po sprawdzeniu tego obiektu.a nie jest null, B zrobi a = 0, a gdy wątek a zyska procesor, zrobi "dzielenie przez zero".

Ten błąd występuje tylko wtedy, gdy wątek A jest poprzedzony tuż po instrukcji if, jest bardzo rzadki, ale to się może zdarzyć.

 29
Author: Steve Gury,
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
2013-05-16 05:08:54

Warunki wyścigu występują w aplikacjach wielowątkowych lub systemach wieloprocesowych. Stan rasy, w najbardziej podstawowym, jest wszystko, co sprawia, że założenie, że dwie rzeczy nie w tym samym wątku lub procesu wydarzy się w określonej kolejności, bez podejmowania kroków w celu zapewnienia, że robią. Dzieje się tak często, gdy dwa wątki przekazują wiadomości, ustawiając i sprawdzając zmienne członkowskie klasy, do której oba mogą mieć dostęp. Prawie zawsze jest stan rasy, gdy jeden wątek wzywa sen, aby dać kolejny wątek czas na zakończenie zadania (chyba że ten sen jest w pętli, z jakimś mechanizmem sprawdzającym).

Narzędzia do zapobiegania rasie są zależne od języka i systemu operacyjnego, ale niektóre z nich to muteksy, sekcje krytyczne i sygnały. Muteksy są dobre, gdy chcesz mieć pewność, że tylko Ty coś robisz. Sygnały są dobre, gdy chcesz się upewnić, że ktoś inny skończył coś robić. Minimalizacja współdzielonych zasobów może również pomóc w zapobieganiu nieoczekiwanym behaviors

Wykrywanie warunków wyścigowych może być trudne, ale jest kilka znaków. Kod, który w dużej mierze opiera się na śpi jest podatny na warunki wyścigowe, więc najpierw sprawdź, czy połączenia do snu w dotkniętym kodzie. Dodawanie szczególnie długich uśpień może być również używane do debugowania, aby spróbować wymusić określoną kolejność zdarzeń. Może to być przydatne do odtwarzania zachowania, sprawdzania, czy można sprawić, że zniknie, zmieniając czas rzeczy, i do testowania rozwiązań wprowadzonych. Na uśpienia powinny być usunięte po debugowaniu.

Znak, że ktoś ma stan rasy jest jednak, jeśli jest problem, który występuje tylko sporadycznie na niektórych maszynach. Typowe błędy to awarie i impasy. Dzięki logowaniu powinieneś być w stanie znaleźć dotknięty obszar i stamtąd wrócić do pracy.

 18
Author: tsellon,
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
2008-08-29 16:12:09

Race condition jest nie tylko związany z oprogramowaniem, ale także ze sprzętem. W rzeczywistości termin ten został początkowo ukuty przez przemysł sprzętowy.

Według Wikipedii :

Termin wywodzi się z idei dwóch sygnałów ścigających się ze sobą do Wpłyń najpierw na wyjście .

Stan rasy w układzie logicznym:

Tutaj wpisz opis obrazka

Branża oprogramowania przyjęła ten termin Bez Modyfikacji, co sprawia, że jest to trochę trudne do zrozumienia.

Musisz zrobić jakiś zamiennik, aby mapować go do świata Oprogramowania:

  • "dwa sygnały" = > "dwa wątki" / "dwa procesy"
  • "wpływ na wyjście" = > "wpływ na jakiś wspólny stan"

Więc stan race w branży oprogramowania oznacza "dwa wątki" / "dwa procesy" ścigające się nawzajem, aby "wpłynąć na jakiś wspólny stan", a końcowy wynik wspólnego stanu będzie zależał od pewnej subtelnej różnicy czasu, co może być spowodowane przez określoną kolejność uruchamiania wątków/procesów, harmonogramowanie wątków/procesów itp.

 11
Author: nybon,
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
2018-09-25 22:54:27

Microsoft opublikował naprawdę szczegółowy Artykuł na temat warunków rasowych i impasów. Najbardziej streszczonym abstraktem z niego byłby akapit tytułowy:

Warunek race występuje, gdy dwa wątki uzyskują dostęp do współdzielonej zmiennej w w tym samym czasie. Pierwszy wątek odczytuje zmienną, a drugi thread odczytuje tę samą wartość ze zmiennej. Wtedy pierwszy wątek i drugi wątek wykonują swoje operacje na wartości i ścigają się na zobacz, który wątek może zapisać wartość jako ostatnią do współdzielonej zmiennej. Zachowywana jest wartość wątku, który zapisuje jego wartość jako ostatni, ponieważ wątek zapisuje wartość, którą poprzedni wątek napisał.

 7
Author: Konstantin Dinev,
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
2012-09-14 08:00:50

Warunek rasy to sytuacja w programowaniu współbieżnym, w której dwa równoległe wątki lub procesy i wynikający z nich stan końcowy zależy od tego, kto pierwszy otrzyma zasób.

 4
Author: Jorge Córdoba,
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
2008-08-29 16:07:57

Stan rasy jest niepożądaną sytuacją, która występuje, gdy urządzenie lub system próbuje wykonać dwie lub więcej operacji w tym samym czasie, ale ze względu na charakter urządzenia lub systemu, operacje muszą być wykonane w odpowiedniej kolejności, aby być wykonane poprawnie.

W pamięci komputera lub pamięci masowej może wystąpić stan wyścigu, jeśli polecenia do odczytu i zapisu dużej ilości danych są odbierane w prawie tym samym momencie, a maszyna próbuje nadpisać niektóre lub wszystkie stare dane, podczas gdy stare dane są nadal odczytywane. Rezultatem może być jedna lub więcej z następujących sytuacji: awaria komputera, "nielegalna operacja", powiadomienie i wyłączenie programu, błędy w odczycie starych danych lub błędy w zapisie nowych danych.

 2
Author: dilbag koundal,
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
2012-04-13 11:29:23

Oto klasyczny przykład salda konta bankowego, który pomoże początkującym zrozumieć wątki w Javie łatwo w.r. t. warunki wyścigu:

public class BankAccount {

/**
 * @param args
 */
int accountNumber;
double accountBalance;

public synchronized boolean Deposit(double amount){
    double newAccountBalance=0;
    if(amount<=0){
        return false;
    }
    else {
        newAccountBalance = accountBalance+amount;
        accountBalance=newAccountBalance;
        return true;
    }

}
public synchronized boolean Withdraw(double amount){
    double newAccountBalance=0;
    if(amount>accountBalance){
        return false;
    }
    else{
        newAccountBalance = accountBalance-amount;
        accountBalance=newAccountBalance;
        return true;
    }
}

public static void main(String[] args) {
    // TODO Auto-generated method stub
    BankAccount b = new BankAccount();
    b.accountBalance=2000;
    System.out.println(b.Withdraw(3000));

}
 2
Author: realPK,
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
2013-05-16 05:12:14

Ok to 4 pytania. jedna po drugiej odpowiedź jest jak poniżej....

Jaki jest stan rasy?

Występuje, gdy wyjście i / lub wynik procesu jest krytycznie zależny od sekwencji lub czasu innych zdarzeń, tj. np.

Jak je wykryć?

Prowadzi do błędu, który jest trudny do zlokalizowania.

Jak sobie z nimi radzisz?

Użycie Semafory

I na koniec

Jak im zapobiec?

Jednym ze sposobów na uniknięcie sytuacji wyścigowej jest użycie mechanizmu blokującego zasoby. ale blokowanie zasobów może prowadzić do impasu. z czym trzeba się uporać.

 2
Author: Adnan Qureshi,
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 14:46:11

Wypróbuj ten podstawowy przykład, aby lepiej zrozumieć kondycję rasy:

    public class ThreadRaceCondition {

    /**
     * @param args
     * @throws InterruptedException
     */
    public static void main(String[] args) throws InterruptedException {
        Account myAccount = new Account(22222222);

        // Expected deposit: 250
        for (int i = 0; i < 50; i++) {
            Transaction t = new Transaction(myAccount,
                    Transaction.TransactionType.DEPOSIT, 5.00);
            t.start();
        }

        // Expected withdrawal: 50
        for (int i = 0; i < 50; i++) {
            Transaction t = new Transaction(myAccount,
                    Transaction.TransactionType.WITHDRAW, 1.00);
            t.start();

        }

        // Temporary sleep to ensure all threads are completed. Don't use in
        // realworld :-)
        Thread.sleep(1000);
        // Expected account balance is 200
        System.out.println("Final Account Balance: "
                + myAccount.getAccountBalance());

    }

}

class Transaction extends Thread {

    public static enum TransactionType {
        DEPOSIT(1), WITHDRAW(2);

        private int value;

        private TransactionType(int value) {
            this.value = value;
        }

        public int getValue() {
            return value;
        }
    };

    private TransactionType transactionType;
    private Account account;
    private double amount;

    /*
     * If transactionType == 1, deposit else if transactionType == 2 withdraw
     */
    public Transaction(Account account, TransactionType transactionType,
            double amount) {
        this.transactionType = transactionType;
        this.account = account;
        this.amount = amount;
    }

    public void run() {
        switch (this.transactionType) {
        case DEPOSIT:
            deposit();
            printBalance();
            break;
        case WITHDRAW:
            withdraw();
            printBalance();
            break;
        default:
            System.out.println("NOT A VALID TRANSACTION");
        }
        ;
    }

    public void deposit() {
        this.account.deposit(this.amount);
    }

    public void withdraw() {
        this.account.withdraw(amount);
    }

    public void printBalance() {
        System.out.println(Thread.currentThread().getName()
                + " : TransactionType: " + this.transactionType + ", Amount: "
                + this.amount);
        System.out.println("Account Balance: "
                + this.account.getAccountBalance());
    }
}

class Account {
    private int accountNumber;
    private double accountBalance;

    public int getAccountNumber() {
        return accountNumber;
    }

    public double getAccountBalance() {
        return accountBalance;
    }

    public Account(int accountNumber) {
        this.accountNumber = accountNumber;
    }

    // If this method is not synchronized, you will see race condition on
    // Remove syncronized keyword to see race condition
    public synchronized boolean deposit(double amount) {
        if (amount < 0) {
            return false;
        } else {
            accountBalance = accountBalance + amount;
            return true;
        }
    }

    // If this method is not synchronized, you will see race condition on
    // Remove syncronized keyword to see race condition
    public synchronized boolean withdraw(double amount) {
        if (amount > accountBalance) {
            return false;
        } else {
            accountBalance = accountBalance - amount;
            return true;
        }
    }
}
 0
Author: Morsu,
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
2013-05-31 15:51:21

Nie zawsze chcesz odrzucić warunek rasy. Jeśli masz flagę, która może być odczytywana i zapisywana przez wiele wątków, a ta flaga jest ustawiona na "done" przez jeden wątek, aby inny wątek przestał przetwarzać, gdy flaga jest ustawiona na "done", nie chcesz, aby ten" stan wyścigu " został wyeliminowany. W rzeczywistości ten może być określany jako łagodny stan rasy.

Jednakże, używając narzędzia do wykrywania stanu rasy, zostanie on zauważony jako szkodliwy stan rasy.

Więcej Szczegółów w sprawie warunków race tutaj, http://msdn.microsoft.com/en-us/magazine/cc546569.aspx .

 0
Author: octoback,
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-09-07 09:11:25

Rozważ operację, która ma wyświetlać liczbę, gdy tylko liczba zostanie zwiększona. ie., gdy tylko CounterThread zwiększy wartość DisplayThread musi wyświetlić ostatnio zaktualizowaną wartość.

int i = 0;

Wyjście

CounterThread -> i = 1  
DisplayThread -> i = 1  
CounterThread -> i = 2  
CounterThread -> i = 3  
CounterThread -> i = 4  
DisplayThread -> i = 4

Tutaj CounterThread często pobiera blokadę i aktualizuje wartość, zanim DisplayThread wyświetli ją. Tutaj istnieje warunek rasy. Stan wyścigu można rozwiązać za pomocą synchronizacji

 0
Author: bharanitharan,
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-07-15 08:06:48

Możesz zapobiec stanowi rasy , Jeśli używasz klas "atomowych". Powodem jest to, że wątek nie oddziela operacji get I set, przykład jest poniżej:

AtomicInteger ai = new AtomicInteger(2);
ai.getAndAdd(5);

W rezultacie będziesz miał 7 w linku "ai". Chociaż wykonałeś dwie czynności, ale obie operacje potwierdzają ten sam wątek i żaden inny wątek nie będzie w to ingerował, oznacza to brak warunków wyścigowych!

 0
Author: Aleksei Moshkov,
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
2017-08-19 18:19:46