TParallel.For – programowanie równoległe i blokada dostępu do zmiennych wspólnych

Odpowiedz Nowy wątek
2019-07-23 09:25
0

Mam tablicę

maks: array[0..9] of  record
  ileCyfr: integer;
  liczba: int64
end;

zdefiniowaną przed pętlą Tparallel.For. Do tej tablicy wszystkie wątki zapisują jednocześnie. Jak zrobić, żeby w momencie zapisywania zablokować dostęp innych wątków do niej? Bez blokady dostaję nieprawidłowe niskie wartości. Interlocked.Add do tej tablicy nie ma zastosowania tutaj bo nie robię dodawania tylko dowolną wartość przypisuję do tablicy dzielonej.

edytowany 7x, ostatnio: furious programming, 2019-07-23 15:41

Pozostało 580 znaków

2019-07-23 09:34
1

Może coś takiego:

  • stwórz zmienną globalną np. typu boolean,
  • wątek który będzie miał ochotę coś zapisać do tablicy najpierw sprawdzi stan zmiennej, jeśli będzie false to zmienia jej stan na true i zaczyna zapisywać. Na koniec, ponownie zmienia stan zmiennej na true.

To samo w innych wątkach i już.

Możesz pokazać w jaki sposób zaprojektowałbyś sprawdzanie tej flagi? - Patryk27 2019-07-23 09:54

Pozostało 580 znaków

2019-07-23 09:44
0

Tak się nie da bo mi pętla pójdzie do przodu i pominie obliczenie jeśli nie może mieć dostępu bo inny wątek go zablokował. Trzeba zatrzymać wątek do czasu gdy drugi go zwolni. Zwykle to się robi przez semafory, sekcje krytyczne itp. Myślałem że po to jest biblioteka system.threading, żeby to uprościć. Tablica temp jest lokalna zadeklarowana wewnątrz Tparaller.for

for k:= 0 to 9 do  // tu dać wait i lock;
  if temp[k]> maks[k].ileCyfr
    then
      begin
        maks[k].ileCyfr:= temp[k];
        maks[k].liczba:= i;
      end;  // end lock;
edytowany 2x, ostatnio: cerrato, 2019-07-23 10:26

Pozostało 580 znaków

2019-07-23 10:16
3

Sekcje krytyczne czyli TCriticalSection a kod pomiędzy Acquire a Release (najlepiej w bloku try finally)
lub zwykłe TThread.Synchronize To drugie to najprostsze ale znacznie gorsze rozwiązanie nie wiem co tam dokładnie robisz ale może nawet spowodować że kod mimo wątków wykona się wolniej niż bez.


Nie odpowiadam na PW w sprawie pomocy programistycznej.
Pytania zadawaj na forum, bo:
od tego ono jest ;) | celowo nie zawracasz gitary | przeczyta to więcej osób a więc większe szanse że ktoś pomoże.
edytowany 4x, ostatnio: kAzek, 2019-07-23 10:23

Pozostało 580 znaków

2019-07-23 10:25
0

Proponuję przeczytać najpierw:
https://pl.wikipedia.org/wiki/Sekcja_krytyczna
a następnie http://wwwold.fizyka.umk.pl/~jacek/dydaktyka/rad/rad4_watki.pdf (zwłaszcza str. 14 - sekcje krytyczne)
http://www.ii.uni.wroc.pl/~wzychla/ra2223/so2.html - pkt. 2 "synchronizacja". Wprawdzie przykłady są w C, ale artykuł dotyczy ogólnych mechanizmów oferowanych przez WinApi, w związku z czym tak samo to można odpalić równie dobrze na Delphi
i jeszcze coś z naszego forum - Jak w Delphi zrealizować obliczenia równoległe przy zastosowaniu wielowątkowości ?

Aczkolwiek przy prostej apce, moim zdaniem to, o czym pisze @robertz68 też się sprawdzi ;)


That game of life is hard to play
I'm gonna lose it anyway
The losing card I'll someday lay
So this is all I have to say

Pozostało 580 znaków

2019-07-23 10:36
0

Nie sprawdzi się bo ogólnie zwykły kod byłby

if not zablokowany
//przełączy wątki
then
  zablokuj (itd....)

Ale system operacyjny przełączy wątki między instrukcjami i spowoduje zapis nieprawidłowych wartości. Własnie po to sa te funkcje API windows żeby blokowac wątki i nie dopuścić do takich rzeczy.
Programowanie wątków istnieje od początku istnienia Delphi ale ja nie po to korzystam z " Tparallel.For" żeby się uczyć tego skomplikowanego mechanizmu użycia wątków z blokowaniem dostępu do wspólnych zmiennych. Musi być jakiś prosty sposób blokowania bo inaczej ta biblioteka "system.threading"nadpisująca API jest bezcelowa.

edytowany 3x, ostatnio: furious programming, 2019-07-23 15:42

Pozostało 580 znaków

2019-07-23 10:42
1

@Patryk27: pozwolisz że zamienię komentarz na zwykłą odpowiedź bo może komuś się to przyda?

Jak oflagować taką sytuację wg mnie mocno zależy w jakim celu używa się wątków.
Można na szybko opisać kilka scenariuszy dlaczego używa się wątków (na 100% znajdziecie dużo innych powodów):

  • ochrona przed zamrażaniem formy,
  • prawdziwa praca równoległa,
  • procedury szeregowe w wątkach.

Pierwszy przypadek prosty, nie uruchamiaj wątku gdy na coś jeszcze czekasz, jak już uruchomisz to w końcu dostaniesz jakiś wynik - lub błąd ale to też jest w końcu jakaś odpowiedź).
Drugi, o dziwo też prosty, uruchamiasz wątki i resztę zrzucasz na system operacyjny.
Trzeci scenariusz jednak może być mocno skomplikowany. Tak naprawdę jest to zwykłe wykonywanie kodu szeregowo ale "wciśnięte" w wątki. Wszystko jest ok, gdy następny wątek nie musi operować na danych poprzedniego lub sobie bez nich poradzi. Oczywiście są wait-y itp ale, przynajmniej w bibliotece PPL wydaje mi się że nie działają idealnie.
Ostatnio, właśni dzięki bibliotece PPL nastała duża moda na wątki i uważam że dobrze, bo programy w których się je używa działają zdecydowanie płynniej ale właśnie, nie zawsze działają dobrze.
Oczywiście, jest alternatywa, czyli zwykłe wątki. Niestety są dużo trudniejsze ale też mają dużo większe możliwości o czym wspominają koledzy.

Jako suplement (heh, suplement do postu - to chyba już przegięcie :) ) napiszę jak ostatnio walczyłem z wątkami.
Napisałem prostą aplikację FMX dla bardzo niedużej sieci sklepów. W każdym ze sklepów pracuje prościutki serwer REST z dostępem do bazy danych towarów i dokumentów zakupowych.
W sklepach pracownicy odpowiedzialni za zakupy mają prostą aplikację na androida gdzie skanują kod kreskowy towaru (są też wersje pod Windows). W odpowiedzi dostają informację ile towaru jest na innych sklepach, po ile te sklepy kupują towar i po ile sprzedają. Dodatkowo widzą także ostatnie 10 zakupów. Korzyści są niesamowite dla klienta, ale mniejsza z tym.

Idealnie pasowało mi aby poszczególne serwery pytać w wątkach, tak też to zrobiłem. Niestety - nie ma żadnych szans aby to dobrze działało. Kombinowałem w każdą stronę. Wszystko się zacinało.
Wg. mnie problemem jest Indy bo tego używa Delphi w komunikacji REST. Równoległe wywołanie kilku procedur wręcz miesza danymi miedzy sklepami. Normalnie jakby jakieś wycieki były.
Dla testu stworzyłem osobne zmienne dla każdego sklepu (bez sensu trochę) aby mieć pewność że to nie kwestia "przenikania" danych między zmiennymi w procedurach wątków. Nic nie pomogło. Zostało Indy.
No i najważniejsze, były to wątki PPL.

Oczywiście, wróciłem do procedur szeregowych i wszystko działa idealnie.
Jak już koledzy wspomnieli, nie zawsze wątki są najlepsze - niestety.

ochrona przed zamrażaniem formy - masz rację, nie trzeba dawać wątku, wystarczy Application.ProcessMessages - cerrato 2019-07-23 10:44

Pozostało 580 znaków

2019-07-23 10:43
1

Ja bym to widział inaczej, moim zdaniem poniższy pseudokod powinien się sprawdzić (przy założeniu, że zmienna sterująca synchronizacją wątków będzie boolean wielkości 1 bajta. Jakby dać tam coś innego, np. integer, to jest ryzyko, że przełączenie wątków nastąpi w chwili, w której część zmiennej zostanie zmieniona, a część pozostanie w poprzedniej wartości, przez co powstanie kaszana. Tak czy siak - lepszą opcją od zmiennej, jest skorzystanie ze specjalnych mechanizmów synchronizacji wątków, o których @kAzek i ja wspomnieliśmy wcześniej.

while (blokada = TRUE) czekaj_az_sie_odblikuje;
blokada := TRUE;
wykonaj_niebezpieczne_operacje;
blokada:= FALSE;

EDIT - lepiej jednak tak nie rób, patrz następny post by @Patryk27


That game of life is hard to play
I'm gonna lose it anyway
The losing card I'll someday lay
So this is all I have to say
edytowany 2x, ostatnio: furious programming, 2019-07-23 15:42

Pozostało 580 znaków

2019-07-23 10:52
2

@cerrato: Twój kod jest podatny na data race nawet przy założeniu, że boolean jest atomowy:

while (blokada) {
  // czekaj
}

// tutaj w międzyczasie (już po zakończeniu pętli, a przed przypisaniem) drugi wątek zmienia flagę na `true`

blokada := true;

// i boom, na tym etapie masz przynajmniej dwa wątki działające razem mimo semafora

Aby zrealizować to poprawnie, należałoby wykorzystać wspieraną hardware'owo instrukcję compare and swap, która wykonuje całą operację atomowo.


edytowany 10x, ostatnio: furious programming, 2019-07-23 15:42
masz rację, nie pomyślałem o tym :( Czyli w takim razie chyba nie ma alternatywy dla "prawdziwej" synchronizacji, prawda? - cerrato 2019-07-23 11:00
Z tego co wiem, bez wsparcia sprzętowego nie da się tej instrukcji zrealizować poprawnie :/ - Patryk27 2019-07-23 11:08
A boolean nie musi być atomowy. To zależy od implementacji. Sam byłem świadkiem akurat w C# śle mechanizm ten sam, że zmieniając architekturę procesora program przestawał być thread safe. Dlatego właśnie konieczne jest stosowanie konstrukcji przewidzianych do wielowątkowości które z definicji są thread safe, a nie przypadkowo bo akurat tak wyszło z architektury procesora czy pamięci. - somedev 2019-07-23 18:30

Pozostało 580 znaków

2019-07-23 10:58
0

mogę zrobić test ale to jest tylko przypuszczenie, nie ma gwarancji że to będzie zawsze działać

while blokada do;
//tu system operacyjny może przełączyć wątki
blokada := TRUE;
(...blok danych)
blokada:= false

A pierwotny problem pozostał czyli jak łatwo zapisać blokadę zmiennych globalnych w wątku z użyciem "Tparallel.For" żeby nie wracać się do początku czyli użycia funkcji API jakie istnieją od kilkunastu lat.

edytowany 1x, ostatnio: furious programming, 2019-07-23 15:42
Jak @Patryk27 napisał wcześniej - ten kod może być niebezpieczny, wprawdzie szanse że się rozjedzie jest niewielka, ale mimo wszystko bym nie ryzykował. - cerrato 2019-07-23 11:04

Pozostało 580 znaków

2019-07-23 11:33
1

Tak jak myślałem program zwrócił błędne wyniki

Zadanie jest takie

There are ten parts to the problem.
(a) Which ten-digit perfect square contains the most zeros?
(b) Which ten-digit perfect square contains the most ones?
(c) Which ten-digit perfect square contains the most twos?
And so on, up to:
(j) Which ten-digit perfect square contains the most nines?

Przy jednym wątku dostaję wynik prawidłowy

minuty=4
sekundy=1
milisekundy=752
najwięcej cyfr dla  0  wynosi  8  dla  1600000000
najwięcej cyfr dla  1  wynosi  6  dla  1101111489
najwięcej cyfr dla  2  wynosi  6  dla  1254222225
najwięcej cyfr dla  3  wynosi  6  dla  3333330225
najwięcej cyfr dla  4  wynosi  7  dla  4434494464
najwięcej cyfr dla  5  wynosi  6  dla  4555575025
najwięcej cyfr dla  6  wynosi  6  dla  4666665969
najwięcej cyfr dla  7  wynosi  6  dla  7907477776
najwięcej cyfr dla  8  wynosi  6  dla  3828886884
najwięcej cyfr dla  9  wynosi  6  dla  2909199969

przy użyciu blokowania wątków metodą zmiennej logicznej (program podaję niżej)

minuty=0
sekundy=7
milisekundy=605
najwięcej cyfr dla  0  wynosi  7  dla  1024000000
najwięcej cyfr dla  1  wynosi  6  dla  1101111489
najwięcej cyfr dla  2  wynosi  6  dla  1254222225
najwięcej cyfr dla  3  wynosi  5  dla  1333637361
najwięcej cyfr dla  4  wynosi  5  dla  1244043441
najwięcej cyfr dla  5  wynosi  4  dla  1045552225
najwięcej cyfr dla  6  wynosi  4  dla  1025664676
najwięcej cyfr dla  7  wynosi  4  dla  1005777796
najwięcej cyfr dla  8  wynosi  5  dla  1288881801
najwięcej cyfr dla  9  wynosi  4  dla  1019971969

bez blokowania zmiennej wspólnej

minuty=0
sekundy=17
milisekundy=630
najwięcej cyfr dla  0  wynosi  5  dla  1090980900
najwięcej cyfr dla  1  wynosi  3  dla  1011685249
najwięcej cyfr dla  2  wynosi  3  dla  1028292489
najwięcej cyfr dla  3  wynosi  3  dla  1368334081
najwięcej cyfr dla  4  wynosi  3  dla  1214034649
najwięcej cyfr dla  5  wynosi  4  dla  1345055625
najwięcej cyfr dla  6  wynosi  4  dla  1245666436
najwięcej cyfr dla  7  wynosi  4  dla  1326707776
najwięcej cyfr dla  8  wynosi  2  dla  1006983289
najwięcej cyfr dla  9  wynosi  3  dla  1063999161

program button1 to zwykły przebieg, button2 to przebieg równoległy, są dwa guziki i memo na formularzu


uses
  system.threading;

function IdKw(liczba: int64): boolean;
var
  p: int64;
begin
  p:= round(sqrt(liczba));
  IdKw:=p*p=liczba;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  maks: array[0..9] of  record
                          ileCyfr: integer;
                          liczba: int64
                        end;
  temp: array[0..9] of integer;
  i,k: int64;
  c: integer;
  s: string;
  hours, minutes, seconds, millisec: word;
  start,koniec,timeDiff: tdatetime;
begin
  start:= now;
  for i:=0 to 9 do
  begin
    maks[i].ileCyfr:= 0;
    maks[i].liczba:= 0;
  end;
  for i := 1000000000 to 9999999999 do
  begin
    for k := 0 to 9 do
      temp[k]:= 0;
    if IdKw(i)
    then
    begin
      s:=IntToStr(i);
      for k:= 0 to 9 do
      begin
        c:= StrToInt(s[k+1]);
        temp[c]:= temp[c]+1;
      end;
      for k:= 0 to 9 do
        if temp[k]> maks[k].ileCyfr
        then
        begin
          maks[k].ileCyfr:= temp[k];
          maks[k].liczba:= i
        end;
    end;
  end;
  memo1.Lines.Add('koniec');
  koniec:= now;
  timeDiff:= koniec - start;
  DecodeTime(timeDiff, hours, minutes, seconds, millisec);
  memo1.Lines.Add('godziny='+IntToStr(hours));
  memo1.Lines.Add('minuty='+IntToStr(minutes));
  memo1.Lines.Add('sekundy='+IntToStr(seconds));
  memo1.Lines.Add('milisekundy='+IntToStr(millisec));
  for k:= 0 to 9 do
    if maks[k].liczba > 0
    then
      memo1.Lines.Add('najwięcej cyfr dla  '+IntToStr(k)+'  wynosi  '+
        IntToStr(maks[k].ileCyfr)+'  dla  '+IntToStr(maks[k].liczba))
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  maks: array[0..9] of  record
                          ileCyfr: integer;
                          liczba: int64
                        end;
  i,k: int64;
  hours, minutes, seconds, millisec: word;
  start,koniec,timeDiff: tdatetime;
  blokada: boolean;
begin
  start:= now; blokada:= false;
  for i:=0 to 9 do
  begin
    maks[i].ileCyfr:= 0;
    maks[i].liczba:= 0;
  end;
  Tparallel.For(1000000000,9999999999, procedure (i:integer)
  var
    k,c : integer;
    s: string;
    temp: array[0..9] of integer;
  begin
    for k := 0 to 9 do
      temp[k]:= 0;
    if IdKw(i)
    then
    begin
      s:=IntToStr(i);
      for k:= 0 to 9 do
      begin
        c:= StrToInt(s[k+1]);
        temp[c]:= temp[c]+1;
      end;
      while blokada do; //test lock
      blokada:=true;   // lock;
      for k:= 0 to 9 do
        if temp[k]> maks[k].ileCyfr
        then
        begin
          maks[k].ileCyfr:= temp[k];
          maks[k].liczba:= i;
        end;
      blokada:= false  // end lock;
    end;
  end );

  memo1.Lines.Add('koniec');
  koniec:= now;
  timeDiff:= koniec - start;
  DecodeTime(timeDiff, hours, minutes, seconds, millisec);
  memo1.Lines.Add('godziny='+IntToStr(hours));
  memo1.Lines.Add('minuty='+IntToStr(minutes));
  memo1.Lines.Add('sekundy='+IntToStr(seconds));
  memo1.Lines.Add('milisekundy='+IntToStr(millisec));
  for k:= 0 to 9 do
    if maks[k].liczba > 0
    then
      memo1.Lines.Add('najwięcej cyfr dla  '+IntToStr(k)+'  wynosi  '+
        IntToStr(maks[k].ileCyfr)+'  dla  '+IntToStr(maks[k].liczba))
end;

end.
edytowany 1x, ostatnio: furious programming, 2019-07-23 15:43

Pozostało 580 znaków

Odpowiedz
Liczba odpowiedzi na stronę

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