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

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.

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ż.

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;
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.

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 ;)

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.

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.

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

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.

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.

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