Wątek przeniesiony 2018-04-07 17:47 z Newbie przez furious programming.

Problem z wątkami pod Windows 10

0

Mam duży problem z wątkami pod Windows 10.
Dotychczas mój program napisany w Delphi XE działał bez wykorzystywania wątków. W dużym uproszczeniu funkcjonował w następujący sposób:

  1. Najpierw uruchamiana była Procedura A. Wywoływanie Procedury A następowało „jedno po drugim” 36 razy (tyle mam zestawów danych do przeliczenia).
  2. Następnie (w oparciu o uzyskane wyniki) uruchamiana była Procedura B. Tym razem wywoływanie Procedury B następowało 10 razy – też jedno po drugim.

Procedura A i B mają swoje podprocedury. Program w konfiguracji jak wyżej tzn. „bez wątków” na Windows 7 wykonywał obliczenia w około 10 sekund.
Jako amator programowania postanowiłem zmodyfikować działanie programu o wielowątkowość:

  1. Najpierw uruchamianych jest 36 Wątków A (zawierających procedurę A) i czekam, aż wszystkie 36 wątków zostanie zakończonych.
  2. Potem uruchamianych jest 10 Wątków B (zawierających procedurę B) i znów czekam, aż wszystkie 10 wątków zostanie zakończonych.

Dla obu wątków stworzyłem procedurę MyTerminate(Sender: TObject), która zgłasza mi kiedy wszystkie 36 Wątków A i 10 Wątków B zostało zakończonych.

Obecnie program w konfiguracji „wielowątkowej” wykonuje obliczenia na Windows 7 w około 5 sekund – zatem upakowanie procedur w wątki dwukrotnie przyśpieszyło działanie programu.

Niestety mój optymizm i satysfakcja, że zrobiłem w amatorskim programowaniu niewielki krok do przodu nie trwał zbyt długo… Po uruchomieniu programu na Windows 10 okazało się, że obliczenia programem „wielowątkowym” trwają uwaga … 2-3 razy dłużej niż programem bez wątków.

Zacząłem analizować tą kuriozalną sytuację i podsumowując sprawa wygląda tak:

Windows 7, procesor i7, 16GB RAM:
Program „bez wątków”: około 10 sekund
Program „z wątkami”: około 5 sekund
Jest ok!

Windows 8, procesor i5, 4GB RAM:
Program „bez wątków”: około 16 sekund
Program „z wątkami”: około 8 sekund
Jest ok!

Na koniec porażka: Windows 10, procesor i7, 32GB RAM:
Program „bez wątków”: około 7 sekund
Program „z wątkami”: około 20 sekund

Program na wielu komputerach z zainstalowanym Windows 10 zachowuje się tak samo – mianowicie konfiguracja "wielowątkowa" sprawia, że program działa absurdalnie – tzn. znacznie dłużej niż w konfiguracji bez wątków.
Bardzo proszę o pomoc i podpowiedź, co może być przyczyną problemu? Podkreślam, że jest to moja pierwsza przygoda z wątkami. Zastanawiam, że czy w przypadku Windows 10 nie jest to np. kwestia priorytetu wątku, czy innych ustawień.

Szukając odpowiedzi znalazłem artykuł, w którym jest mowa o problemach Windows 10 z szybkim
zamykaniem procesów

3

Bez kodu to można tylko wróżyć. Ale 36 wątków to nie jest dobry pomysł - rdzeni procesora, nawet z HT, masz z pewnością mniej. 36 : 4 = 9; spróbuj odpalić dziewięć wątków a w każdym z nich wykonać obliczenia cztery razy. Albo odwrotnie. Podobnie zrób dla drugiego etapu.

Pytanie też w jaki sposób Procedura B zależy od Procedury A. Czy musisz czekać aż wszystkie A się zakończą, zanim zaczniesz z B?

0

Rozbiłem to na czynniki pierwsze. Sprawa ma się tak, że 36 Wątków A uruchomianych jednocześnie wykonywane jest zawsze błyskawicznie.
Windows 10 nie radzi sobie natomiast z 10 Wątkami B. O dziwo tylko Windows 10!!! Bo jak napisałem na Windows 7 i 8 wszystko działa poprawnie. Stąd upatruję przyczyny właśnie w Windowsie 10.

0

Może to wcale nie Windows, tylko inny procesor… albo „coś źle robisz”.

0

@Majster: sprawdź na różnych komputerach z Win10. Sam system raczej na pewno nie ma zepsutej obsługi wątków, więc przyczyny szukałbym w sprzęcie i jego konfiguracji.

1

Dziękuję Wam za podpowiedzi !

Znalazłem rozwiązanie problemu i chciałem się nim podzielić, bo może komuś się kiedyś przyda.
Przypomnę, że mój problem polegał na tym, że program wielowątkowy działał poprawnie na Windows 7 i Windows 8 dając czasy obliczeń około 5 sekund.

Ten sam program uruchomiony (na różnych komputerach) ale pod systemem Windows 10 zamulał strasznie – tzn. czas obliczeń wynosił około 25 sekund...

Przyczyna problemu okazała się kuriozalna. Mianowicie w jednej z procedur, która była niestety wykonywana wielokrotnie w wątku miałem przyporządkowanie do zmiennej w oparciu o wartości kontrolki TEdit:

z:=StrToFloat(edtEditZ.Text);

Po usunięciu tego przyporządkowania (które było wykonywane wielokrotnie) i zastąpieniu go odwołaniem bezpośrednio do zmiennej globalnej również na Windows 10 czas obliczeń wynosi około 4 sekundy – przypomnę, że poprzednio 25 sekund:

z:=rzedna_Z;

Tak jak napisałem rozwiązanie wydaje się kuriozalne, gdyż zarówno system Windows 7 jak i Windows 8 odczyt tekstu z kontrolki TEdit i jego zamianę na Real realizuje błyskawicznie, podczas gdy pod Windows 10 trwa to nieporównywalnie dłużej (o 20 sekund dłużej...).

Oczywiście jest to moje niedopatrzenie natomiast dzielę się spostrzeżeniem, że program z owym niedopatrzeniem na Windows 7 i Windows 8 działał szybko i bez problemu, a Windows 10 zastrajkował.

Dzięki

5

W wątku innym niż główny wątek GUI nie powinno odwoływać się do kontrolek.
Reguła ta obowiązuje „od zawsze”. Pod Delphi masz procedurę Synchronize do tego celu, ale jest to dość kosztowne wywołanie.

0

Czytałem o Synchronize ale uznałem (może błędnie), że będzie to mało efektywne, gdy wątki będą czekać na siebie. Zatem przerobiłem kod tak, aby na przykład zapis do pliku XML odbywał się po zakończeniu wszystkich wątków.
Jestem "nowicjuszem" więc chętnie zasięgnę języka w kwestii wątków. Mianowicie generator wątków w Delphi wstawia taki komentarz:

{ Important: Methods and properties of objects in visual components 
can only be used in a method called using Synchronize, for example, 
 
      Synchronize(UpdateCaption); 
 
  and UpdateCaption could look like, 
 
    procedure TMojWatek.UpdateCaption; 
    begin 
      Form1.Caption := 'Updated in a thread'; 
    end; }

Czy dobrze rozumiem, że powyższy komentarz dotyczy TYLKO ustawiania właściwości kontrolek przez wątek np. Label1.Caption:='wartość'?
Jeśli dobrze rozumiem, to nie dotyczy to natomiast odczytu ich właściwości np. z:=StrToFloat(Edit1.Text).

2

@Majster
synchronize stosuj ZAWSZE kiedy w wątku odwołujesz się do kontrolek. Nigdy nie wiadomo jak w środku wygląda kod kontrolki i tym samym jak się zachowa kiedy równocześnie będzie wywoływany z różnych wątków.

0

Jeżeli pod tym odczytem Edit1.Text kryje się wywołanie WinAPI GetWindowText, to spowoduje to wysłanie komunikatu WM_GETTEXT do kontrolki i czekanie na odpowiedź. Powinno działać (choć nie ma pewności), ale to "ciężka" operacja, nie tylko zwykłe odczytanie zmiennej, choć na taką wygląda.

0

Rozumiem. Dziękuję za wyjaśnienie.

0
Majster napisał(a):

Czy dobrze rozumiem, że powyższy komentarz dotyczy TYLKO ustawiania właściwości kontrolek przez wątek np. Label1.Caption:='wartość'?
Jeśli dobrze rozumiem, to nie dotyczy to natomiast odczytu ich właściwości np. z:=StrToFloat(Edit1.Text).

Dobrze myślisz i masz rację, a tym samym nie zgadzam się tym co napisał @grzegorz_so. To nie dotyczy zawsze, tylko wtedy gdy zmieniasz stan obiektu (np. kontrolki). Z kontrolkami wizualnymi jest również ten kłopot, że one działają w kontekście głównego wątka aplikacji - cały VCL tak działa.

Generalnie synchronizacja jest niezbędna wtedy, gdy wiele różnych wątków może zmieniać stan jednej instancji obiektu/zmiennej.
Jeśli tylko czytasz, to synchronizacja nie jest niezbędna.
Ale, to zależy... być może jest tak (a szczerze - po prostu nie wiem) jak napisał @Azarien (odczyt de-facto wywołuje inne akcje, które powinny być synchronizowane) no i mamy kłopot.

1
wloochacz napisał(a):
Majster napisał(a):

Czy dobrze rozumiem, że powyższy komentarz dotyczy TYLKO ustawiania właściwości kontrolek przez wątek np. Label1.Caption:='wartość'?
Jeśli dobrze rozumiem, to nie dotyczy to natomiast odczytu ich właściwości np. z:=StrToFloat(Edit1.Text).

Dobrze myślisz i masz rację, a tym samym nie zgadzam się tym co napisał @grzegorz_so. To nie dotyczy zawsze, tylko wtedy gdy zmieniasz stan obiektu (np. kontrolki). Z kontrolkami wizualnymi jest również ten kłopot, że one działają w kontekście głównego wątka aplikacji - cały VCL tak działa.

Generalnie synchronizacja jest niezbędna wtedy, gdy wiele różnych wątków może zmieniać stan jednej instancji obiektu/zmiennej.

Nieprawda, wystarczy, że JEDEN zmienia a inny/inne wątki czytają i już synchronizacja jest niezbędna.

Jeśli tylko czytasz, to synchronizacja nie jest niezbędna.

Bzdury.

Ale, to zależy... być może jest tak (a szczerze - po prostu nie wiem) jak napisał @Azarien (odczyt de-facto wywołuje inne akcje, które powinny być synchronizowane) no i mamy kłopot.

To już zupełnie oddzielny temat i może być omawiany w kontekście konkretnej kontrolki.

1
kolaborant napisał(a):
wloochacz napisał(a):
Majster napisał(a):

Czy dobrze rozumiem, że powyższy komentarz dotyczy TYLKO ustawiania właściwości kontrolek przez wątek np. Label1.Caption:='wartość'?
Jeśli dobrze rozumiem, to nie dotyczy to natomiast odczytu ich właściwości np. z:=StrToFloat(Edit1.Text).

Dobrze myślisz i masz rację, a tym samym nie zgadzam się tym co napisał @grzegorz_so. To nie dotyczy zawsze, tylko wtedy gdy zmieniasz stan obiektu (np. kontrolki). Z kontrolkami wizualnymi jest również ten kłopot, że one działają w kontekście głównego wątka aplikacji - cały VCL tak działa.

Generalnie synchronizacja jest niezbędna wtedy, gdy wiele różnych wątków może zmieniać stan jednej instancji obiektu/zmiennej.

Nieprawda, wystarczy, że JEDEN zmienia a inny/inne wątki czytają i już synchronizacja jest niezbędna.

Ekhm... czy ja napisałem coś innego?

Jeśli tylko czytasz, to synchronizacja nie jest niezbędna.

Bzdury.

Tak?
A dlaczego?

Bzdury, to wypisujesz w innym wątku w którym do teraz nie doczekałem się niczego.

Ale, to zależy... być może jest tak (a szczerze - po prostu nie wiem) jak napisał @Azarien (odczyt de-facto wywołuje inne akcje, które powinny być synchronizowane) no i mamy kłopot.

To już zupełnie oddzielny temat i może być omawiany w kontekście konkretnej kontrolki.

Bla, bla... i co z tego wynika ponad to co napisałem?

0
wloochacz napisał(a):

Generalnie synchronizacja jest niezbędna wtedy, gdy wiele różnych wątków może zmieniać stan jednej instancji obiektu/zmiennej.

Nieprawda, wystarczy, że JEDEN zmienia a inny/inne wątki czytają i już synchronizacja jest niezbędna.

Ekhm... czy ja napisałem coś innego?

A co? Nie wiesz co napisałeś? Przeczytaj jeszcze raz powyższe zdania.

Jeśli tylko czytasz, to synchronizacja nie jest niezbędna.

Bzdury.

Tak?
A dlaczego?

Bo odczyt też musi być atomowy jeśli chcesz odczytać poprawne dane. To chyba oczywiste?

Bzdury, to wypisujesz w innym wątku w którym do teraz nie doczekałem się niczego.

Nudzą mnie przepychanki z mędrcami.

Ale, to zależy... być może jest tak (a szczerze - po prostu nie wiem) jak napisał @Azarien (odczyt de-facto wywołuje inne akcje, które powinny być synchronizowane) no i mamy kłopot.

To już zupełnie oddzielny temat i może być omawiany w kontekście konkretnej kontrolki.

Bla, bla... i co z tego wynika ponad to co napisałem?

To, że nie do końca rozumiesz po co jest synchronizacja.

0

Witam,

Mam do Was ostatnie pytanie dotyczące wątków ;) Mianowicie skoro odczyt danych z kontrolek przez uruchomione instancje wątków może (choć nie musi) powodować błędy np.: z:=StrToInt(Edit.Text) zatem bardzo proszę o potwierdzenie, że podejście, które chcę zastosować jest poprawne:

  1. Aby wyeliminować ewentualne błędy spowodowane próbą odczytu przez wątki danych z kontrolki w tej samej "milisekundzie" (w tym samym momencie), przed uruchomieniem wątków przepisuję dane z kontrolek do zmiennych globalnych np: z:=StrToInt(Edit.Text).
  2. Następnie uruchamiam wątki i one w swoich procedurach moją odwołanie już nie do kontrolek, ale do zmiennych globalnych (np. zmiennej z).

I tu pytanie, czy zmienne globalne to takie cwane „ustrojstwo”, które bez problemu poradzi sobie z jednoczesnym odczytem danych przez różne wątki (odczytem w tym samym momencie)? Pytanie moje pojawia się ponieważ oświeciliście mnie, że kontrolki nawet podczas odczytu danych wysyłają komunikaty i wówczas może, choć nie musi, powodować to błędy (pod Windows 10 powoduje). Zatem, czy odczyt w tym samym momencie zmiennej globalnej alokowanej w pamięci jest rozwiązaniem bezpiecznym? Mam na myśli odczyt przez wątki zmiennej globalnej, a nie zmiennej sekcji private w ciele wątku?
Odpowiedź na to pytanie wyjaśni mi, czy wszystkie zmienne, które odczytują moje wątki muszę „zdublować” i umieszczać bezwzględnie w sekcji private w ciele wątku, czy może zmienne globalne gwarantują taki rodzaj przechowywania danych, że mogę je jednocześnie odczytywać bezkolizyjnie?

Z góry dziękuję za wyrozumiałość ... ;)

0

Problem nie jest z odczytem w tym samym momencie, problem jest z zapewnieniem że odczyt następuje faktycznie po zapisie, co często nie jest takie oczywiste.
To co opisujesz (osobna zmienna - tylko nie nazywaj jej z…) ma dużą szansę działać.

0

Ta zmienna to string więc tylko kwestią przypadku jest zaistnienie lub nie, równoczesnego zapisu i odczytu .
Po co robić dziwne fikołki jeśli są sprawdzone narzędzia do wielowątkowego dostępu (zapis/odczyt) do zasobu np. sekcje krytyczne ?

EDT.
Teraz zauważyłem że z jest intem i to będzie działać

Edit1:
Przykład z sekcją krytyczną:

Var
    getSetValueCriticalSection: trtlcriticalsection;
    commonString:string;


function getValue: string;
begin
  try
    entercriticalsection(getSetValueCriticalSection);
    result := commonString;
  finally
    leavecriticalsection(getSetValueCriticalSection);
  end;
end;

procedure setValue(aValue: string);
begin
  try
    entercriticalsection(getSetValueCriticalSection);
    commonString := aValue;
  finally
    leavecriticalsection(getSetValueCriticalSection);
  end;
end;

procedure TForm2.Edit3Change(Sender: TObject);
begin
   setValue(tedit(sender).text);
end;
0

Dziękuję Wam za wyjaśnienia, a grzegorz_so za konkretny przykład. Z tego wniosek, że muszę koniecznie poczytać o tych sekcjach krytycznych. Wątki to wyższa szkoła jazdy ;)

Pytając o dostęp do zmiennych (typu Integer, Boolean czy Real) w moim przypadku są to zmienne "tylko z nazwy". Mam na myśli to, że do tych zmiennych globalnych przed uruchomieniem wątków zapisuję parametry z kontrolek:

obliczenia_wariantowe:=RadioButton.Checked;
szerokosc:=StrToFloat(Edit1.Text);
liczba_iteracji:=StrToInt(Edit2.Text);

Następnie podczas wykonywania wątków te zmienne (będące w rzeczywistości parametrami obliczeń) nie są zmieniane! Dlatego też pytałem o odczyt zmiennych.

Rozumiem, że w takiej sytuacji nie będzie problemu z odczytem przez wątki zmiennych typu Integer, Boolean czy Real natomiast String to już inna bajka?

0

Sekcje krytyczne to nie jest jedyny mechanizm pozwalający na synchronizację wielowątkowego dostępu do wspólnych zasobów. Są jeszcze mutexy i semafory , ale w tym przypadku sekcje krytyczne dobrze się sprawdzają, są proste w implementacji i wydajne w sensie niewielkiego obciążenia CPU

1
Majster napisał(a):

Rozumiem, że w takiej sytuacji nie będzie problemu z odczytem przez wątki zmiennych typu Integer, Boolean czy Real […]

Z tymi typami danych nie miałbyś problemu nawet wtedy, gdyby wątki modyfikowały zawartość tych zmiennych. Operacje atomowe nie wymagają synchronizacji, a tym samym zabezpieczeń w postaci np. sekcji krytycznych.

[…] natomiast String to już inna bajka?

Inna bajka, choć obstawiam, że niektóre operacje modyfikujące zawartość ciągu również mogą być atomowe.

1

Nie rozumiem dokładnie co chcesz zrobić ale skoro wartość zmiennych przypisujesz tylko na początku wątków i później one nie robią nic takiego że jeden wątek coś robi ze zmienną a później drugi ma korzystać z nowej wartości zmiennej to powinna wystarczyć deklaracja jako threadvar wtedy automatycznie będziesz operował na kopiach zmiennych.

0

Sekcja krytyczna to jest taka armata na muchę w większości przypadków. WinAPI udostępnia całą serię funkcji o nazwach zaczynających się od Interlocked (np. InterlockedExchange), semafory, muteksy - do poszczególnych zastosowań, po to by właśnie uniknąć "ciężkiej" sekcji krytycznej.

1 użytkowników online, w tym zalogowanych: 0, gości: 1