2012-03-19 6 views
5

Programuję aplikację wielowątkową. Mam dwa wątki. Włączony służy do przesyłania niektórych danych z urządzenia do globalnego bufora danych, a drugi do zapisywania tych danych do pliku.Najlepsza praktyka, jak czekać na dane w wątku i zapisać go w pliku?

Dane z urządzenia do bufora przesyłają się asynchronicznie. Celem drugiego wątku powinno być oczekiwanie na zapisanie określonej ilości danych w głównym buforze danych i na koniec zapisanie pliku do pliku.

Cóż, pierwszy wątek znajduje się w DLL, a drugi w głównej aplikacji. Tymczasowo rozwiązuję to z wydarzeniami. Pierwszy wątek przesyła dane z urządzenia do głównego bufora danych i zlicza dane, a po przekazaniu określonej ilości danych ustawia zdarzenie. Drugi oczekuje na sygnalizację zdarzenia, a gdy jest uruchomiony, uruchamia kod dla magazynu danych. Proste, ponieważ to działa.

Thread1.Execute: 

    var DataCount, TransferedData: Integer; 

    DataCounter := 0; 
    while not Terminted do 
    begin 
    TransferData(@pData, TransferedData); 
    Inc(DataCounter, TransferedData) 
    if DataCounter >= DataCountToNotify then SetEvent(hDataCount); 
    end; 



    Thread2.Execute: 

    hndlArr[0] := hDataCount; 
    hndlArr[1] := hTerminateEvent; 

    while (not Terminated) do 
    begin 
    wRes := WaitForMultipleObjects(HandlesCount, Addr(hndlArr), false, 60000); 
    case wRes of 
     WAIT_OBJECT_0: 
     begin 
      Synchronize(WriteBuffer);     // call it from main thread 
      ResetEvent(hndlArr[0]); 
     end; 
     WAIT_OBJECT_0 + 1: 
     begin 
      ResetEvent(hTerminateEvent); 
      break; 
     end; 
     WAIT_TIMEOUT: Break; 
    end; 
    end; 

Teraz chciałbym zrobić drugi wątek bardziej niezależne ... więc mogę zrobić wiele wystąpień drugiego wątku i nie trzeba czekać na pierwszym wątku. Chciałbym przenieść część kodu zliczającą dane z pierwszego wątku do drugiego, więc nie będę już potrzebować liczenia danych w pierwszym wątku. Pierwszy z nich będzie przeznaczony wyłącznie do celów przesyłania danych.

Chciałbym użyć drugiego jako licznika danych i do przechowywania danych. Ale teraz będę musiał zapętlić i ciągle sprawdzać ręcznie dla określonej ilości danych. Gdybym miał pętlę while, będę musiał dodać trochę snu, więc drugi wątek nie zmniejszy wydajności komputera, ale nie wiem, jak długo powinienem spać, podczas gdy prędkość transferu danych w wątku początkowym nie jest stała, a zatem szybkość zliczania w drugim wątku Bedzie się różnić.

Zgaduję, że ten przykładowy kod nie jest dobre: ​​

Thread2.Execute: 
    var DataCount: Integer; 
    DataIdx1 := GetCurrentDataIdx; 
    while (not Terminated) do 
    begin  
    if (GetCurrentDataIdx - DataIdx1) >= DataCountToNotify then 
    begin 
     Synchronize(WriteBuffer); 
     DataIdx1 := GetCurrentIdx; 
    end; 
    sleep(???); 
    end; 

Więc moje pytanie brzmi: co jest najlepszym sposobem, aby rozwiązać ten problem z liczeniem danych i przechowywanie go w drugim wątku? Jakie są twoje doświadczenia i sugestie?

+3

Dlaczego wymuszasz zapis pliku do głównego wątku, 'Synchronizuj (WriteBuffer)'? Nie rozumiem, w jaki sposób uda ci się pomnożyć drugi wątek i zachować kolejność zapisu w jednym pliku. –

+0

Wątek zostanie pomnożony, ale dane będą przechowywane w różnych plikach. Nigdzie nie napisałem, że napiszę to do tego samego pliku. Moim celem jest posiadanie wielu kontrolek, które są w stanie przechowywać dane z tego samego źródła do różnych plików. – Nix

+0

Dobrze wiedzieć, ponieważ odpowiedź będzie zależała od tych informacji. Wciąż zastanawiam się, dlaczego zapis pliku musi być egzekwowany do głównego wątku. –

Odpowiedz

5

Masz pewne problemy. @LU RD już wskazał jedną z nich - nie synchronizuj rzeczy, które nie wymagają synchronizacji. Nie jest jasne, co robi "WriteBuffer", ale system plików i wszystkie bazy danych, których użyłem, są w porządku, aby jeden wątek otwierał plik/tabelę i zapisywał do nich.

Twój system buforowy prawdopodobnie wykona pewną uwagę. Czy istnieje "określona ilość danych" lub czy jest to pewna hipotetyczna liczba, która pozwala na leniwą pisanie?

Zwykle wątki producenta i konsumenta wymieniają wiele wskaźników bufora w kolejkach, dzięki czemu unikają współdzielenia jednego bufora. Biorąc pod uwagę, że jest to biblioteka DLL, więc wywoływanie zarządzania pamięcią może być problematyczne, prawdopodobnie uniknęłbym ich również, tworząc pulę buforów podczas uruchamiania, aby przesyłać dane dookoła systemu. Chciałbym użyć klasy bufora, a nie tylko wskaźników do pamięci, ale nie jest to absolutnie wymagane (po prostu o wiele łatwiejsze/elastyczne/bezpieczne).

Pętle Sleep() to spektakularnie zły sposób komunikowania się między wątkami. Sleep() ma swoje zastosowania, ale to nie jest jedna z nich. Delphi/Windows ma wiele mechanizmów synchronizacji - zdarzeń, semaforów, muteksów itp. - sprawia, że ​​takie zbieranie niepotrzebne.

LU RD wspomniał również o problemach z równoległym przetwarzaniem danych, których kolejność musi zostać zachowana. Często wymaga to kolejnego wątku, kolekcji w stylu listy i numerów sekwencyjnych. Nie spróbowałbym tego, dopóki nie uzyskasz dobrej komunikacji między wątkami.

+0

Kod górny służy wyłącznie do celów demonstracyjnych. Nie przejmuj się Synchronizuj. Mam już naprawione rzeczy i używam odpowiedniego mechanizmu do synchronizowania wątków itp. Jako mentione chciałbym policzyć dane, a gdy warunek dotyczy np. DataCountToNotify = 8192 Chciałbym przechowywać dane. Ale chciałbym uniknąć snu. Czy jest jakaś inna alternatywa, jak to zrobić? – Nix

+0

@Następnie, jeśli się nie mylę, tak długo, jak twoja nić robi "coś" w pętli while, możesz pominąć "sen", ale możesz użyć snu (1), który jest snem od 1 do 55 ms (win xp ma problem, nie ma pewności co do przyszłych wersji) – ComputerSaysNo

+1

@DorinDuminica, zobacz [wątek-sen-to-oznaka-o-kiepsko-zaprojektowanego-programu] (https://msmvps.com/ blogs/peterritchie/archive/2007/04/26/thread-sleep-is-a-sign-of-a-poor-designed-program.aspx) dla argumentów przeciwko użyciu "uśpienia" w wątku. –

2

Jeśli chcesz uniknąć wywołania Sleep() w drugim wątku, użyj sprawdzanego timera, takiego jak TSimpleEvent.

Ustaw czas uśpienia, aby obsłużyć wszystkie warunki czasowe. Jest jedna korzyść z używania tego schematu zamiast normalnego Sleep(), ponieważ programowalny licznik czasu nie spowoduje włączenia wątku w głęboki sen.

Aby usunąć wątek, zobacz komentarze w kodzie.

var 
    FEvent: TSimpleEvent; 
    FSleepTime: Integer = 100; // Short enough to handle all cases 

Constructor TThread2.Create; 
begin 
    Inherited Create(False); 
    FEvent := TSimpleEvent.Create; 
    Self.FreeOnTerminate := True; 
end; 

procedure TThread2.Execute; 
var 
    DataCount: Integer; 
begin 
    DataIdx1 := GetCurrentDataIdx; 
    while (fEvent.WaitFor(FSleepTime) = wrTimeout) do 
    begin 
    if Terminated then 
     break; 
    // Do your work 
    if (GetCurrentDataIdx - DataIdx1) >= DataCountToNotify then 
    begin 
     // Write data to buffer 
     DataIdx1 := GetCurrentIdx; 
    end; 
    end; 
end; 

// To stop the thread gracefully, call this instead of Terminate or override the DoTerminate 
procedure TThread2.SetTerminateFlag; 
begin 
    FEvent.SetEvent; 
end; 
+0

LU RD dzięki temu wygląda bardzo obiecująco. Będę musiał zmienić czas FSleep, w zależności od prędkości transferu danych w pierwszym wątku. Mogę obliczyć ten czas. Tak jak mówiłem, staram się unikać snu w wątku, dlatego szukałem lepszej alternatywy dla snu. Nigdy nie użyłem snu w żadnym zaprogramowanym przeze mnie wątku. Nie uważam tego za bardzo bezpieczne. Lubię system, żeby był bardziej zdeterminowany. Wielkie dzięki. Postaram się zrealizować to w sposób, który zasugerowałeś. – Nix

+0

Jest to uzasadnione na pierwszy rzut oka, ale tak nie jest. Czy chodziło Ci o "wrSignaled" zamiast "wrTimeout"? Ponadto, nadal ma to wywołanie Sycnhronize, które sprawia, że ​​wątek jest gorszy niż nieskuteczny - w rzeczywistości wprowadzi więcej opóźnień. –

+0

@MartinJames, Synchronizacja jest ewidentnie błędna, ale PO powiedział, że to już nie jest problem. Pętla będzie nadal działać, ale nie zostanie zasygnalizowana (każda milisekunda FSleepTime). W moim umyśle wygląda to dobrze. –

Powiązane problemy