APLIKACJA WIELOWĄTKOWA problem z funkcją

dział ogólny

APLIKACJA WIELOWĄTKOWA problem z funkcją

Nowy postprzez zeno32167 » środa, 3 września 2008, 09:38

Próbowałem przerobić skrypt z Cyfrowego Barona ale nic nie udało mi sie zdziałać.

Jak przerobić o tą funkcję

Kod: Zaznacz cały
int __fastcall Wyliczenia2(Pointer Parameter)
{
for(int i = 100000 ; i > 0; i--)
{
  Form7->Edit2->Text = IntToStr(i);
  Sleep(100);
}
ExitThread(GetExitCodeThread(Wyliczenia2, NULL)); // usunięcie wątku z pamięci, od tego momentu wątku nie można już wstrzymać.
}


na taką funkcję do której wysyłam zmienną 'i' o coś jak tą:

Kod: Zaznacz cały
int __fastcall Wyliczenia2(Pointer Parameter,int i )
{
for(int i ; i > 0; i--)
{
  Form7->Edit2->Text = IntToStr(i);
  Sleep(100);
}
ExitThread(GetExitCodeThread(Wyliczenia2, NULL)); // usunięcie wątku z pamięci, od tego momentu wątku nie można już wstrzymać.
}


i jak potem przy wywołaniu tej funkcji

Kod: Zaznacz cały
W_ID = BeginThread(NULL, 0, Wyliczenia2, this, 0, W_PD);


dodać ten parametr 'i' do jej polecenia wywołania?

Dzięki za pomoc
Avatar użytkownika
zeno32167
Bladawiec
Bladawiec
 
Posty: 23
Dołączył(a): sobota, 30 sierpnia 2008, 17:15
Podziękował : 0
Otrzymał podziękowań: 0
    NieznanyNieznana

Re: APLIKACJA WIELOWĄTKOWA problem z funkcją

Nowy postprzez Cyfrowy Baron » środa, 3 września 2008, 11:39

Nie możesz zmieniać nagłówka funkcji wątku tak skonstruowanego, czyli nie możesz tam wstawić dodatkowego argumentu.
Musisz sterować tą zmienną poprzez zadeklarowanie zmiennej globalnej.
Czyli wywołujesz funkcję normalnie, a przed wywołaniem ustawiasz wartość zmiennej, np.

plik nagłówkowy:
Kod: Zaznacz cały
public:
        int i;


plik żródłowy:
Kod: Zaznacz cały
    int __fastcall Wyliczenia2(Pointer Parameter,)
    {
      for(Form1->i ; Form1->i > 0; Form1->i--)
      {
        Form7->Edit2->Text = IntToStr(i);
        Sleep(100);
      }
      ExitThread(GetExitCodeThread(Wyliczenia2, NULL)); // usunięcie wątku z pamięci, od tego momentu wątku nie można już wstrzymać.
    }
//-------------------------------------------------------------------
  void __fastcall Form1::Button1Click(TObject *Sender)
  {
    i = 0
    W_ID = BeginThread(NULL, 0, Wyliczenia2, this, 0, W_PD);
  }
Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    NieznanyNieznana

Re: APLIKACJA WIELOWĄTKOWA problem z funkcją

Nowy postprzez kinio » środa, 3 września 2008, 14:51

Witam!

Jak już kiedyś pisałem na tym forum, najważniejszą kwestią w programowaniu wielowątkowym jest synchronizacja dostępu do obszaru pamięci współdzielonej z innymi wątkami potomnymi jak również macierzystym. Nie jestem przekonany czy podane powyżej rozwiązanie zapewnia odpowiedni poziom zabezpieczenia synchronizacji. Pokaże inne rozwiązanie, niekoniecznie łatwiejsze, i krótsze, ale pewne. Jeżeli ktoś koniecznie chce używać metod BeginThread itp. to może coś poczytać o synchronizacji, może modyfikator __thread będzie przydatny, tego nie wiem dokładnie.

W tym problemie wątek odwołuje się do pamięci współdzielonej (zmienna i, pole Edit, obiekt formatki), co może być niebezpieczne i w każdym momencie może spowodować wyjątek!!!

Borland posiada specjalną klasę TThread, która może być użyta do konstrukcji nowych wątków i dostarcza odpowiednich mechanizmów zabezpieczenia pamięci.
Do poprawnego użycia klasy wystarczy zdefiniować konstruktor oraz funkcję Execute, która jest funkcją czysto wirtualną, a której instrukcję są wykonywane jako nowy wątek. Jeżeli nie zamierzamy naszej klasy dziedziczyć dalej to najprostsza definicja klasy może wyglądać np. tak:
Kod: Zaznacz cały
class myThread : public TThread
{
     void __fastcall Execute(void);

public:

    myThread(void):TThread(true){}
};

Teraz w dowolnym pliku .cpp dołączamy plik nagłówkowy z powyższą definicją i definiujemy funkcję Execute:
Kod: Zaznacz cały
void __fastcall myThread::Execute(void)
{
     // Instrukje wykonywane jako osobny wątek
}

Aby uruchomić dany wątek wystarczą dwie linijki:
Kod: Zaznacz cały
myThread* t = new myThread;
t->Resume();

Teraz to o co mi chodzi: synchronizacja pamięci. Do tego celu należy użyć funkcji Synchronize, która jako parametr przyjmuje wskaźnik do funkcji będącej metodą klasy, która jest typu void i nie przyjmuje żadnych parametrów. Tak więc aby poprawnie i bezpiecznie można było pobrać wartość i należy zdefiniować odpowiednią do tego funkcje (np. get_i) i wywołać ją wewnątrz ciała funkcji Execute przy pomocy funkcji Synchronize. Trzeba też zadbać o miejsce zapisania pobranej wartości, czyli w definicji ciała klasy definiujemy nowe pole niech będzie też i, teraz definicja klasy będzie następująca:
Kod: Zaznacz cały
class myThread : public TThread
{
     int i;

     void __fastcall get_i(void);
     void __fastcall Execute(void);

public:

    myThread(void):TThread(true){}
};

definicja ciała funkcji get_i powinna wyglądać np. tak:
Kod: Zaznacz cały
void __fastcall myThread::get_i(void)
{
     i = Form1->i;
}

natomiast w funkcji Execute należy umieścić instrukcję:
Kod: Zaznacz cały
Synchronize(get_i);

Po wywołaniu tej metody wartość pola i należącego do obiektu Form będzie skopiowana do pola należącego do obiektu wątku w sposób bezpieczny. Takie pobieranie wartości zmiennej jest tylko przykładem, prostszym sposobem jest przekazanie tej wartości do obiektu wątku przed uruchomieniem za pomocą odpowiednio zdefiniowanego konstruktora, lub też specjalnej metody np:
Kod: Zaznacz cały
myThread(int _i):TThread(true), i(_i){}

wtedy utworzenie obiektu będzie wyglądało następująco:
Kod: Zaznacz cały
myThread* y = new myThread(Form1->i);
y->Resume();

Takie przekazanie wartości jest jak najbardziej bezpieczne gdyż wątek jeszcze nie jest uruchomiony, a co następuje w kolejnej linii. Do zmiennych zdefiniowanych wewnątrz klasy myThread wątek może odwoływać się bezpiecznie.

Najważniejsze dla tego przykładu jest odpowiednie zdefiniowanie funkcji wyświetlania w polu Edit: definiujemy funkcję np. display_value która będzie wyświetlać daną zmienną w polu Edit. Tak dla uogólnienia nie będziemy wyświetlać wartości pola i bo wątpię aby to było celem autora tego postu. Wyświetlać będziemy inną zmienną która tylko będzie przyjmować wartość i ale też może dowolnie inną. W tym celu definiujemy nowe pole w klasie np. value, czyli nasza klasa z nowym konstruktorem będzie wyglądać tak:
Kod: Zaznacz cały
class myThread : public TThread
{
     int i;
     int value;

     void __fastcall display_value(void);
     void __fastcall Execute(void);

public:
    myThread(int _i):TThread(true), i(_i){}
};

natomiast definicja funckji display_value tak:
Kod: Zaznacz cały
void __fastcall myThread::display_value(void)
{
     Form1->Edit1->Text = Form1->Edit1->Text + String(value);
}

oraz ciało funkcji Execute:
Kod: Zaznacz cały
void __fastcall myThread::Execute(void)
{
     for(;i>=0;--i)
     {
          value = i;
          Synchronize(display_value);
     }
}


Jeszcze raz powtarzam odpowiednie zabezpieczenie przed wielodostępem do pamięci współdzielonej jest najważniejsze w programowaniu wielowątkowym. Przedstawiony sposób jest pewnie jednym z wielu. Nie wiem czy Borland oferuje coś w rodzaju mutex-ów, albo semaforów które może w bardziej prosty sposób pasował by do pierwotnego przykładu autora tego postu. Można ewentualnie poszukać odpowiednich bibliotek.
Jeżeli ktoś mi nie wież jak ważne jest zabezpieczenie przed wielodostępem, to niech przerobi podany przeze mnie przykład na taki aby w funkcji Execute znajdowały się instrukcje np. rysowania np. na Canvas. Program się zaraz wyłoży. Dla przykładu, jeżeli już mamy gotową klasę jak przedstawiłem, to w funkcji Execute można umieścić taki oto kod:
Kod: Zaznacz cały
for(int h=0;h<Form1->ClientHeight;++h)
          for(int w=0;w<Form1->ClientWidth;++w)
               Form1->Canvas->Pixels[w][h] = RGB(w,h,0);

i dla różnicy ten sam kod w funkcji display_value i wywołać w funkcji Execute zawierającą instrukcję:
Kod: Zaznacz cały
Synchronize(display_value)
.
Jak pisałem program nie zawsze generuje wyjątek, wtedy można kilka razy powtórzyć operację, albo nawet próbować na innych komputerach.
Aha, wywołanie funkcji Synchronize powoduje że aplikacja się zachowuje jak jednowątkowa, dlatego w metodach wywoływanych przez tą funkcję trzeba umieszczać jak najmniejszą ilość instrukcji, najlepiej tylko te odwołujące się do zasobów współdzielonych, tak aby wątki mogły się jak najdłużej wykonywać równolegle.

Mam nadzieje że ten post będzie przydatny!
Pozdr! i powodzenia!
If a machine is expected to be infallible, it cannot also be intelligent.
-- A.Turing
Avatar użytkownika
kinio
Homos antropiczny
Homos antropiczny
 
Posty: 67
Dołączył(a): poniedziałek, 14 lipca 2008, 08:51
Podziękował : 0
Otrzymał podziękowań: 0
    NieznanyNieznana

Re: APLIKACJA WIELOWĄTKOWA problem z funkcją

Nowy postprzez zeno32167 » środa, 3 września 2008, 18:53

Wielkie dzięki za pomoc.

Skorzystałem z porady Cyfrowego Barona i dorzuciłem Enable = false przycisku start na samym początku wykonywania funkcji a na końcu funkcji dałem true. Akurat to mi wystarczyło ponieważ miałem tylko jedną funkcję do wykonania a zależało mi na tym żebym mógł ją w każdej chwili zakończyć i żeby aplikacja mi się nie zawieszała dopuki funkcja nie skonczy działania(ponieważ do tej pory po wciśnięciu start, przycisk był cały czas wciśnięty i aplikacja jakby zawieszona ponieważ działała funkcja, i dopiero jak funkcja skończyła działanie to aplikacja się odwieszała i mogłem nacisnąć STOP - który już był mi nie potrzebny bo funkcja zakończyła dzialanie). A teraz funkcja sobie działa a ja mogę przycisnąć STOP w każdej chwili i przerwać działanie funkcji.

Z tym zabezpieczeniem dostępu wielowątkowego do pamięci współdzielonej- dobra sprawa, przyda mi się ale dopiero w następnym projekcie(dopiero nad nim rozmyślam). Narazie to ja zaczynam z c++ builder przygodę.

Dzięki za pomoc, nie pierwszy i podejrzewam że nie ostani raz :)
Avatar użytkownika
zeno32167
Bladawiec
Bladawiec
 
Posty: 23
Dołączył(a): sobota, 30 sierpnia 2008, 17:15
Podziękował : 0
Otrzymał podziękowań: 0
    NieznanyNieznana

Re: APLIKACJA WIELOWĄTKOWA problem z funkcją

Nowy postprzez kinio » środa, 3 września 2008, 19:08

Witam ponownie!!

Szkoda że nie opisałeś swojego problemu dokładniej, że chodzi Ci tylko o to aby aplikacja się nie zawieszała podczas działania funkcji i żeby można ją było w każdej chwili przerwać. Jest inne rozwiązanie które nie wymaga takich mechanizmów jak wątki. Wystarczy co jakiś czas przetworzyć listę komunikatów wysłanych do twojej aplikacji.
Jeżeli masz jeszcze ochotę wypróbować inne rozwiązanie zrób tak. W pliku nagłówkowym zadeklaruj zmienna np. bool continue_loop:
Kod: Zaznacz cały
class TForm1 : public TForm
{
__published:   // IDE-managed Components
     TButton *Button1;
     TMemo *Memo1;
     TButton *Button2;
     void __fastcall Button1Click(TObject *Sender);
     void __fastcall Button2Click(TObject *Sender);
private:   // User declarations
public:      // User declarations
     __fastcall TForm1(TComponent* Owner);
     bool continue_loop;   // <<< ---------------
};

W twoją funkcję zmień dodając nowe instrukcje:
Kod: Zaznacz cały
continue_loop = true;
for(int i = 100000 ; (i > 0) && (continue_loop); i--)
{
  Form7->Edit2->Text = IntToStr(i);
  Sleep(100);
  Application->ProcessMessages();
}

i teraz pod przyciskiem którym zatrzymujesz funkcje dodaj:
Kod: Zaznacz cały
continue_loop = false;

Zatrzyma to pętlę ponieważ warunek trwania pętli nie będzie spełniony. Dodatkowo dzięki przetwarzaniu listy komunikatów program cały czas będzie reagował na np. Klikanie myszka, przesuwanie, itp.

Podaję taki sposób bo tak też można, chyba nawet łatwiej niż wplatając w to wątki.

Pozdr!
If a machine is expected to be infallible, it cannot also be intelligent.
-- A.Turing
Avatar użytkownika
kinio
Homos antropiczny
Homos antropiczny
 
Posty: 67
Dołączył(a): poniedziałek, 14 lipca 2008, 08:51
Podziękował : 0
Otrzymał podziękowań: 0
    NieznanyNieznana

Re: APLIKACJA WIELOWĄTKOWA problem z funkcją

Nowy postprzez Cyfrowy Baron » środa, 3 września 2008, 19:58

Też mu to sugerowałem w poście WM_KEYDOWN WM_KEYUP jaka funkcja to obsluży? jak się domyślam ten post jest logicznym rozwinięciem tamtego.

Przetwarzanie listy komunikatów w tym konkretnym przypadku może nie wystarczyć, gdyż pozwala co prawda przerwać działanie funkcji (tutaj bardziej pętli), ale nie pozwala na wywoływanie innych funkcji w trakcie działania tej konkretnej. Wątek pozwala uruchomić tą konkretna funkcję, ale program może jednocześnie wykonywać inne operacje w trakcie działania tej konkretnej funkcji.
Avatar użytkownika
Cyfrowy Baron
Administrator
Administrator
 
Posty: 4716
Dołączył(a): niedziela, 13 lipca 2008, 15:17
Podziękował : 12
Otrzymał podziękowań: 442
System operacyjny: Windows 7 x64 SP1
Kompilator: Embarcadero RAD Studio XE2
C++ Builder XE2 Update 4
SKYPE: cyfbar
Gadu Gadu: 0
    NieznanyNieznana


  • Podobne tematy
    Odpowiedzi
    Wyświetlone
    Ostatni post

Powrót do Ogólne problemy z programowaniem

Kto przegląda forum

Użytkownicy przeglądający ten dział: Brak zalogowanych użytkowników i 42 gości

cron