Czy lepiej jest użyć TThread ' s "Synchronize" lub użyć Wiadomości okien dla IPC między głównym i potomnym wątku?

Mam dość prostą wielowątkową aplikację VCL GUI napisaną w Delphi 2007. Wykonuję pewne przetwarzanie w wielu wątkach potomnych (do 16 jednoczesnych), które wymagają aktualizacji kontroli siatki w moim głównym formularzu (po prostu wysyłanie ciągów do siatki). Żaden z wątków dziecięcych nigdy ze sobą nie rozmawia.

Mój początkowy projekt polegał na wywołaniu TThread ' s "Synchronize", Aby zaktualizować formularz kontroli siatki w aktualnie działającym wątku. Rozumiem jednak, że wywołanie Synchronize zasadniczo wykonuje się tak, jakby było głównym wątkiem podczas wywołania. Z maksymalnie 16 wątków uruchomionych na raz (a większość przetwarzania wątku potomnego trwa od

Uruchomiłem go w tym momencie, gdy wątek potomny publikuje wiadomość windows (składającą się z rekordu kilku ciągów), a główny wątek ma słuchacz i po prostu aktualizuje siatkę, gdy wiadomość jest odbierana.

Wszelkie opinie na najlepszą metodę dla IPC w tej sytuacji? Komunikaty okien czy "synchronizacja"?

Jeśli używam komunikatów okienkowych, czy sugerujesz zawinięcie kodu, w którym zamieszczam do siatki w bloku TCriticalSection (enter and leave)? Czy nie będę musiał martwić się o bezpieczeństwo wątku, ponieważ piszę do siatki w głównym wątku(chociaż w funkcji obsługi wiadomości okna)?

Author: Mick, 2009-11-27

3 answers

Edit:

Wygląda na to, że wiele szczegółów implementacji zmieniło się od czasu Delphi 4 i 5 (wersje Delphi nadal używam do większości moich prac), a Allen Bauer skomentował:

Od D6, TThread nie używa już SendMessage. Wykorzystuje kolejkę roboczą bezpieczną dla wątku, w której znajduje się "praca" przeznaczona dla głównego wątku. Do głównego wątku wysyłana jest wiadomość informująca, że praca jest dostępna, a wątek tła blokada wydarzenia. Gdy główna pętla komunikatów ma przejść bezczynnie, wywołuje "CheckSynchronize", aby sprawdzić, czy jakaś praca czeka. Jeśli tak, to je przetwarza. Po zakończeniu elementu roboczego Zdarzenie, w którym wątek tła jest zablokowany, jest ustawiane w celu wskazania zakończenia. Wprowadzony w D2006, TThread.Dodano metodę Queue, która nie blokuje.

Dzięki za korektę. Przyjmij więc szczegóły w oryginalnej odpowiedzi z przymrużeniem oka.

Ale to nie do końca wpływa na podstawowe punkty. Nadal utrzymuję, że cała idea Synchronize() jest fatalnie błędna, a będzie to oczywiste w momencie, gdy ktoś spróbuje zająć kilka rdzeni nowoczesnej maszyny. Nie" synchronizuj " swoich wątków, pozwól im pracować, dopóki nie zostaną ukończone. Spróbuj zminimalizować wszystkie zależności między nimi. Szczególnie podczas aktualizacji GUI nie ma absolutnie powodu, aby czekać na zakończenie. Niezależnie od tego, czy Synchronize() używa SendMessage() Czy PostMessage(), uzyskaną blokadą drogi jest to samo.


To, co tu prezentujesz, wcale nie jest alternatywą, ponieważ Synchronize() używa SendMessage() wewnętrznie. Więc to bardziej pytanie, jakiej broni chcesz użyć, aby strzelić sobie w stopę.

Synchronize() jest z nami od czasu wprowadzenia TThread w Delphi 2 VCL, co jest naprawdę wstyd, ponieważ jest to jeden z większych błędów projektowych w VCL.

Jak to działa? Używa wywołania SendMessage() do okna, które zostało utworzone w głównym wątku i ustawia wiadomość parametry do przekazania adresu wywołanej metody obiektu bez parametru. Ponieważ wiadomości Windows będą przetwarzane tylko w wątku, który utworzył Okno docelowe i uruchomi pętlę wiadomości, spowoduje to zawieszenie wątku, obsługę wiadomości w kontekście głównego wątku VCL, wywołanie metody i wznowienie wątku dopiero po zakończeniu wykonywania metody.

Więc co jest z nim nie tak (i co jest podobnie nie tak z używaniem SendMessage() bezpośrednio)? Kilka rzeczy:

  • zmuszenie dowolnego wątku do wykonania kodu w kontekście innego wątku wymusza dwa przełączniki kontekstowe wątku, które niepotrzebnie spala cykle procesora.
  • podczas gdy wątek VCL przetwarza wiadomość, aby wywołać metodę zsynchronizowaną, nie może przetworzyć żadnej innej wiadomości.
  • gdy więcej niż jeden wątek użyje tej metody, wszystkie zablokują i poczekają na Synchronize() lub SendMessage(), aby powrócić. Tworzy to gigantyczne wąskie gardło.
  • jest impas czekający na zdarza się. Jeśli wątek wywoła Synchronize() lub SendMessage() podczas trzymania obiektu synchronizacji, a wątek VCL podczas przetwarzania wiadomości musi nabyć ten sam obiekt synchronizacji, aplikacja zostanie zablokowana.
  • to samo można powiedzieć o wywołaniach API oczekujących na uchwyt wątku - użycie WaitForSingleObject() lub WaitForMultipleObjects() bez pewnych środków do przetwarzania wiadomości spowoduje impas, jeśli wątek potrzebuje tych sposobów "synchronizacji" z innym wątkiem.

Więc czego użyć zamiast tego? Kilka opcje, opiszę kilka:

  • Użyj PostMessage() zamiast SendMessage() (lub PostThreadMessage(), Jeśli oba wątki nie są wątkiem VCL). Ważne jest jednak, aby nie używać żadnych danych w parametrach wiadomości, które nie będą już ważne po przybyciu wiadomości, ponieważ wątki wysyłania i odbierania nie są w ogóle zsynchronizowane, więc należy użyć innych środków, aby upewnić się, że dowolny ciąg znaków, odniesienie do obiektu lub fragment pamięci są nadal ważne, gdy wiadomość jest przetwarzana, nawet jeśli wątek wysyłania i odbierania nie jest zsynchronizowany. wysyłanie wątku może już nie istnieć.

  • Twórz bezpieczne dla wątków struktury danych, przesyłaj do nich dane z wątków roboczych i wykorzystuj je z głównego wątku. Użyj PostMessage() tylko, aby ostrzec wątek VCL, że przybyły nowe dane do przetworzenia, ale nie publikuj wiadomości za każdym razem. Jeśli masz ciągły strumień danych, możesz nawet mieć VCL thread ankieta dla danych( może za pomocą timera), ale to jest tylko wersja biednego człowieka.

  • Nie używaj niskich narzędzia poziomu w ogóle, już nie. Jeśli jesteś przynajmniej na Delphi 2007, Pobierz OmniThreadLibrary i zacznij myśleć w kategoriach zadań, a nie wątków. Biblioteka ta ma wiele udogodnień do wymiany danych między wątkami i synchronizacji. Posiada również implementację puli wątków, co jest dobrą rzeczą - ilość wątków, których powinieneś użyć, zależy nie tylko od aplikacji, ale także od sprzętu, na którym jest uruchomiony, więc wiele decyzji można podjąć tylko w czasie wykonywania. OTL pozwoli Ci aby uruchamiać zadania na wątku puli wątków, aby system mógł dostroić liczbę równoległych wątków w czasie wykonywania.

Edit:

Przy ponownym czytaniu zdaję sobie sprawę, że nie zamierzasz używać SendMessage(), ale PostMessage() - cóż, niektóre z powyższych nie mają wtedy zastosowania, ale zostawię to na swoim miejscu. Jednak jest jeszcze kilka punktów w twoim pytaniu, które chcę poruszyć: {]}

Z maksymalnie 16 wątkami działającymi jednocześnie (a większość przetwarzania wątków potomnych zajmuje od

Jeśli publikujesz wiadomość z każdego wątku raz na sekundę lub nawet dłużej, projekt jest w porządku. Nie powinieneś wysyłać setek lub więcej wiadomości na wątek na sekundę, ponieważ kolejka wiadomości Windows ma ograniczoną długość i niestandardowe wiadomości nie powinny zbytnio zakłócać normalnego przetwarzania wiadomości (twój program zacznie wydawać się nie reagujący).

Gdzie dziecko w związku z tym, że system windows nie jest w pełni funkcjonalny, nie jest w pełni funkcjonalny.]}

Komunikat okna nie może zawierać rekordu. Posiada dwa parametry, jeden typu WPARAM, drugi typu LPARAM. Można tylko rzucić wskaźnik do takiego rekordu do jednego z tych typów, więc żywotność rekordu musi być zarządzany w jakiś sposób. Jeśli dynamicznie go przydzielasz, musisz go również zwolnić, co jest podatne na błędy. Jeśli przekazujesz wskaźnik do rekordu na stosie lub do pola obiektu, którego potrzebujesz aby upewnić się, że jest on nadal ważny, gdy wiadomość jest przetwarzana, co jest trudniejsze dla wysłanych wiadomości niż dla wysłanych wiadomości.

Sugerujesz owinięcie kodu, w którym umieszczam go na siatce w bloku TCriticalSection (enter and leave)? Czy nie będę musiał martwić się o bezpieczeństwo wątku, ponieważ piszę do siatki w głównym wątku(chociaż w funkcji obsługi wiadomości okna)?

Nie ma potrzeby tego robić, ponieważ PostMessage() wywołanie powróci natychmiast, więc synchronizacja nie jest konieczna w tym momencie . Na pewno będziesz musiał martwić się o bezpieczeństwo wątku, niestety nie możesz wiedzieć kiedy . Musisz upewnić się, że dostęp do danych jest bezpieczny dla wątków, przez zawsze blokowanie danych dla dostępu, za pomocą obiektów synchronizacji. Tak naprawdę nie ma sposobu, aby to osiągnąć dla rekordów, dane zawsze mogą być dostępne bezpośrednio.

 36
Author: mghie,
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-11-01 12:02:58

BTW, można też użyć TThread.Queue() zamiast TThread.Synchronize(). Queue() jest wersją asynchroniczną, nie blokuje wątku wywołującego:

(Queue jest dostępny od D8).

Wolę Synchronize() lub Queue(), ponieważ jest o wiele łatwiejszy do zrozumienia (dla innych programistów) i lepszy OO niż zwykłe wysyłanie wiadomości (bez kontroli nad tym lub potrafi debugować!)

 9
Author: André,
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-06-22 07:23:20

Chociaż jestem pewien, że jest dobra i zła droga. Napisałem kod przy użyciu obu metod, a ta, do której wciąż wracam, to metoda SendMessage i nie jestem pewien, dlaczego.

Użycie SendMessage vs Synchronize nie robi żadnej różnicy. Oba działają zasadniczo tak samo. Myślę, że powodem, dla którego wciąż używam SendMessage, jest to, że dostrzegam większą kontrolę, ale nie wiem.

Procedura SendMessage powoduje, że wątek wywołujący wstrzymuje się i czeka aż Okno docelowe kończy przetwarzanie wysłanej wiadomości. Z tego powodu główny wątek aplikacji jest zasadniczo synchronizowany z wywołującym wątkiem potomnym na czas trwania połączenia. Nie musisz używać sekcji krytycznej w oknie obsługi wiadomości systemu windows.

Transmisja danych jest zasadniczo jednokierunkowa, od wątku wywołującego do głównego wątku aplikacji. W wiadomości można zwrócić wartość typu integer.wynik, ale nic, co wskazuje na obiekt pamięci w głównym wątku.

Ponieważ dwa wątki są "zsynchronizowane" w tym punkcie, a główny wątek aplikacji jest obecnie związany, odpowiadając na SendMessage, nie musisz się również martwić o inne wątki wchodzące i niszczące dane w tym samym czasie. Więc nie nie musisz się martwić o użycie sekcji krytycznych lub innych środków bezpieczeństwa gwintów.

Dla prostych rzeczy można zdefiniować pojedynczą wiadomość (wm_threadmsg1) i użyć pól wparam i lparam do przesyłania (integer) wiadomości o statusie tam i z powrotem. Dla bardziej złożonych przykładów można przekazać ciąg znaków, przekazując go przez lparam i typecasting go z powrotem do longinta. A-la longint(pchar (myvar)) lub użyj pwidechar, jeśli używasz D2009 lub nowszego.

Jeśli masz już to działa z metod synchronizacji, to nie martwiłbym się o przeróbkę go, aby dokonać zmiany.

 5
Author: Vivian Mills,
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
2009-11-27 02:45:25