Elektroda.pl
Elektroda.pl
X

Search our partners

Find the latest content on electronic components. Datasheets.com
Elektroda.pl
Please add exception to AdBlock for elektroda.pl.
If you watch the ads, you support portal and users.

program dwu watkowy w c++

tomcio1-19 24 Nov 2006 09:40 2198 7
  • #1
    tomcio1-19
    Level 10  
    Mam prosbe - czy ktos by umial napisac najprostszy jaki mozna program uruchamiajacy chociazby dwa watki jednoczesnie(przykladowo moze wyswietlac puste okna windows); program ma byc w c++ borland; prosze tylko o sam kod bez kompilacji[funkcja obslugi watku i opis jej] . Za wszystko z gory dzieki.
    Kamery 3D Time of Flight - zastosowania w przemyśle. Darmowe szkolenie 16.12.2021r. g. 10.00 Zarejestruj się
  • #2
    shg
    Level 35  
    A masz, ale nie w C++, tylko w C, nie kilka, a jeden wątek i nie Borland, ale powinno działać.
    Wycięte z innego mojego programu, istotne komentarze po polsku napisałem.

    Jak chcesz więcej wątków (tu jest 1), to potrzeban jest tablica "uchwytów" (hjakisThread i wskazników pjakisThread), dla każdego wątku po jednym.
    Wywołujesz kilka razy funkcję CreateThread()
    Tylko na kilku wątkach trzeba inaczej rozwiązać sygnalizację zdarzeń. Główny program musi użyć funkcji WaitForMultipleObjects() z TRUE, zamiast FALSE i tablicą z uchwytami wątków jako argumentami, będzie wtedy czekał na zakończenie wszystkich wątków.
    Event do sygnalizacji wyjścia (threadEvent[0]) należy stworzyć jako nieautomatyczny, żeby nie zresetoweał go pierwszy wątek, który na niego zareaguje (jedno TRUE zamiast FALSE, czy odwrotnie w CreateEvent(), patrz dokumentacja).

    Code:
    #include <windows.h>
    

    LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM);

    /*  Make the class name into a global variable  */
    char szClassName[] = "Beat Detector";
    HWND hwnd;              /* Main window handle */

    #define NEVENTS (3)
    HANDLE            threadEvent[NEVENTS];   /* tak na przykład ze trzy zdarzenia */

    HANDLE              hjakisThread;
    DWORD            pjakisThread;

    DWORD WINAPI jakisThread(LPVOID data) {
    DWORD   eventn;

       while(1) {
          /* czeka na zasygnalizowanie któregoś z Eventów
          Zamiast INFINITE można podać czas oczekiwania w milisekundach,
          po którym funkcja zwraca wartość WAIT_TIMEOUT
          Jak wątek sobie tak czeka, to praktycznie nie zużywa czasu CPU */
          eventn = WaitForMultipleObjects(NEVENTS, threadEvent, FALSE, INFINITE);
          if(eventn != WAIT_FAILED) {         /* if there was no error */
             if(eventn != WAIT_TIMEOUT) {         /* jak nie uplynal czas, to znaczy ze zostalo cos zasygnalizowane  */
                eventn -= WAIT_OBJECT_0;        /* get number of event */
                if(eventn == 0) {               /* Event nr 0 używany jest do sygnalizowania konieczności zakończenia wątku  */
                   ExitThread(0);            /* Zakończ wątek */
                } else if((eventn >0) && (eventn <=(NEVENTS-1))) {
                   /* a tu cokolwiek innego, eventn zawiera numer zasygnalizowanego zdarzenia  */
                }
             } else {
                /* a tu coś co będzie wykonywane co czas ustalony zamiast INFINITE */
             }
          }
       }
       ExitThread(0);  /* normal termination */
    }



    /* Inicjalizacja tablicy zdarzeń */
    BOOL SetupEvents(void) {
    UINT i;
        for (i = 0; i < NEVENTS; i++)
        {
          /* Domyślnie tworzone jako automatyczne, niezasygnalizowane, odsyłam do dokumentacji */
            rghEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
            if (NULL == rghEvent[i]) return FALSE;
        }
        return TRUE;
    }


    /****************************************************/

    int WINAPI WinMain (HINSTANCE hThisInstance,
                        HINSTANCE hPrevInstance,
                        LPSTR lpszArgument,
                        int nFunsterStil)

    {
        MSG messages;            /* Here messages to the application are saved */
        WNDCLASSEX wincl;        /* Data structure for the windowclass */
       UINT wReturn;

       if(SetupCreateEvents() == FALSE) {
            MessageBox(NULL, "CreateEvent() failed", "Error", MB_OK);
            return 1;
       }

       hjakisThread = CreateThread(NULL,   /* pointer to thread security attributes */
                      0,   /* initial thread stack size, in bytes, 0 = default */
                       jakisThread,                  /* pointer to thread function */
                       0,                           /* ta wartość zostanie przekazana nowemu wątkowi jako argument 'data' */
                       0,                           /* creation flags */
                       &pjakisThread                   /* pointer to returned thread identifier */
                      );

       if(haudioThread == FALSE) {
            MessageBox(NULL, "CreateThread() failed", "Error", MB_OK);
            return 1;
       }

       /* Wyzszy priorytet dla wątku, tak dla przykładu.
       mi to potrzebne do odtwarzania dźwięku, żeby się nie ciął */
       SetThreadPriority(hjakisThread, THREAD_PRIORITY_ABOVE_NORMAL);

        /* The Window structure */
        wincl.hInstance = hThisInstance;
        wincl.lpszClassName = szClassName;
        wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
        wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
        wincl.cbSize = sizeof (WNDCLASSEX);

        /* Use default icon and mouse-pointer */
        wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
        wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
        wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
        wincl.lpszMenuName = NULL;                 /* No menu */
        wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
        wincl.cbWndExtra = 0;                      /* structure or the window instance */
        /* Use Windows's default color as the background of the window */
        wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

        /* Register the window class, and if it fails quit the program */
        if (!RegisterClassEx (&wincl))
            return 1;

        /* The class is registered, let's create the program*/
        hwnd = CreateWindowEx (
               0,                   /* Extended possibilites for variation */
               szClassName,         /* Classname */
               "Okno zaczepno obronne",       /* Title Text */
               WS_OVERLAPPEDWINDOW, /* default window */
               CW_USEDEFAULT,       /* Windows decides the position */
               CW_USEDEFAULT,       /* where the window ends up on the screen */
               544,                 /* The programs width */
               375,                 /* and height in pixels */
               HWND_DESKTOP,        /* The window is a child-window to desktop */
               NULL,                /* No menu */
               hThisInstance,       /* Program Instance handler */
               NULL                 /* No Window Creation data */
               );

        /* Make the window visible on the screen */
        ShowWindow (hwnd, nFunsterStil);

    /* Można też zrobić tak, że wątek będzie sygnalizował różne zdarzenia głównemu programowi za pomocą eventów
    W tym celu jako warunku wykonywania głównej pętli należy użyć funkcji MsgWaitForMultipleObjects(),
    a wewnątrz niej zamiast GetMessage() funkcji PeekMessage() */

        while (GetMessage (&messages, NULL, 0, 0)) {
          TranslateMessage(&messages);
          DispatchMessage(&messages);
        }

       /* Jeżeli PC znalazł się tutaj, to znaczy że program kończy działanie */

       /* trzeba zasygnalizować wątkowi konieczność zakończenia działania */
       SetEvent(threadEvent[0]);

       /* Tę wartość trzeba ustawić na cokolwiek innego niż WAIT_OBJECT_0, które oznacza, że wątek zakończył działanie  */
       DWORD threadrc=WAIT_TIMEOUT;
       /* poczekaj na zakończenie wątku, ale nie dłużej niż 0.5s */
       while(threadrc != WAIT_OBJECT_0) {
          /* Argumentami funkcji WaitForSingleObject(), WaitForMultipleObjects() i podobnych mogą być
          nie tylko wskaźniki na Eventy, ale i na wątki, semafory itp. patrz dokumentacja */
          threadrc = WaitForSingleObject(hjakisThread, 500);
          /* Jeżeli przekroczono czas oczekiwania */
          if(threadrc == WAIT_TIMEOUT) {
             if(MessageBox(NULL, "Can't finish thread (timeout).\nWait another 500ms?", "Error", MB_YESNO) == IDNO)
                /* Jak się kliknie na "Nie", to zawieszony wątek zostanie zignorowany i zabije go system
                Wątek można też zabić "ręcznie" za pomocą TerminateThread() chyba */
                 break;
          }
       }

        /* The program return-value is 0 - The value that PostQuitMessage() gave */
        return messages.wParam;
    }


    /*  This function is called by the Windows function DispatchMessage()  */

    LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    PAINTSTRUCT ps;
    HDC hdc;


        switch (message)                  /* handle the messages */
        {
           case WM_CREATE:

          /* Tu są tworzone przyciski i inne duperelki */
          
          case WM_PAINT:

             hdc = BeginPaint (hwnd, &ps);
                /* Tu można coś narysować */
             EndPaint (hwnd, &ps);
               
             return 0;

            case WM_COMMAND:

             /* A tu się obsługuje przyciski itp. */
             /* Na przykład w ten sposób:
             /* if( LOWORD(wParam)==ID_PRZYCISKU && HIWORD(wParam)==BN_CLICKED) {
                SetEvent(threadEvent[0]);
             }
             Można zasygnalizować wątkowi konieczność zrobienia czegoś po naciśnięciu przycisku w oknie */

    /* Called when CLOSE signal is sent to window (e.g. by pressing the 'X' button) */
          case WM_CLOSE:
             DestroyWindow(hwnd);
             return 0;

            case WM_DESTROY:
                PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
                break;

            default:
                return DefWindowProc (hwnd, message, wParam, lParam);
        }
        return 0;
    }
  • #3
    tomcio1-19
    Level 10  
    ooo dzieki ale wlasnie mi chodzi o przynajmniej 2 watki - chodzi dokladnie o funkcje je obslugujaca, jak nimi zarzadza
  • #4
    shg
    Level 35  
    A proszę. Ale nie dwa, tylko 50, bo tak :P
    Większość pisana z pamięci, niekoniecznie musi się skompliwać, ale mniej więcej tak to MOŻE wyglądać, sposobów na komunikację jest całe mnóstwo. Za ewentualne błędy i niedomówienia odpowiada Albin Kolano.
    Code:
    #include <windows.h>
    
    #include <process.h>

    LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM);

    /*  Make the class name into a global variable  */
    char szClassName[] = "Beat Detector";
    HWND hwnd;              /* Main window handle */

    #define NEVENTS (3)                     /* Ilość zdarzeń */
    #define NTHREADS (50)                  /* ilość wątków */
    HANDLE            threadEvent[NEVENTS];   /* tak na przykład ze trzy zdarzenia */

    HANDLE              hjakisThread[NTHREADS]; /* "uchwyty", dla każdego po jednym */

    /* do tworzenia wątku używana jest tym razem funkcja _beginthread(), stąd zamiast
       DWORD WINAPI jakisThread(LPVOID data)
    funkcja zadeklarowana jest jak poniżej*/
    void __cdecl jakisThread(void * data) {
    DWORD   eventn;

       while(1) {
          /* czeka na zasygnalizowanie któregoś z Eventów
          Zamiast INFINITE można podać czas oczekiwania w milisekundach,
          po którym funkcja zwraca wartość WAIT_TIMEOUT
          Jak wątek sobie tak czeka, to praktycznie nie zużywa czasu CPU */
          eventn = WaitForMultipleObjects(NEVENTS, threadEvent, FALSE, INFINITE);
          if(eventn != WAIT_FAILED) {         /* if there was no error */
             if(eventn != WAIT_TIMEOUT) {         /* jak nie uplynal czas, to znaczy ze zostalo cos zasygnalizowane  */
                eventn -= WAIT_OBJECT_0;        /* get number of event */

                if((eventn >= 0) && (eventn <= (NEVENTS-1))) {
                   /* eventn zawiera numer zasygnalizowanego zdarzenia
                   Ogolnie to takie zamieszanie lekkie to wprowadza, bo na zasygnalizowanego eventa
                   zareagować może dowolny z wątków, dlatego tu nastąpi
                   rozdział funkcji pomiędzy poszczególne wątki.
                   Pierwszy wątek, który się załapie sprawdzi sobie który event został zasygnalizowany
                   i wykona odpowiednią operację.
                   Zaletę ogromną ma to taką, że pozwala na przykład na zbalansowanie czasu CPU
                   między wieloma wątkami, bo gdy jeden z nich będzie zajęty na przykład obliczeniami,
                   sygnał odbierze inny, czekający wątek i zacznie wyświetlać wyniki tychże obliczeń,
                   a właściwie to nie tego co jest aktualnie liczone, a tego, co zostało wyliczone już wcześniej
                   i jest gotowe do wyświetlenia.
                   Można oczywiście (a nawet należy) bawić się w mutexy, semafory i sekcje krytyczne,
                   żeby zapewnić bezkolizyjny dostęp do danych, na przykład wątek wyświetlający wyniki
                   będzie musiał poczekać aż wątek wykonujący obliczenia zakończy działanie,
                   do tego celu służą między innymi funkcje EnterCriticalSection() i LeaveCriticalSection(),
                   pierwsza z nich czeka aż sekcja zostanie zwolniona, na przykład przez inny wątek,
                   który po zakończeniu operacji wywoła LeaveCriticalSection(). W jakiejś książce
                   spotkałem się z opisem, z którego wynikało, że kod pomiędzy Enter...() i Leave...()
                   wykonuje się bez przerwy, znaczy że na ten czas wyłączany jest multitasking, co jest
                   niestety (a może i na szczęście) bzdurą */
                   switch(eventn) {
                   case 0:   /* Event nr 0 używany jest do sygnalizowania konieczności zakończenia wątku  */
                      _endthread();            /* Zakończ wątek */
                      break;
                   case 1:
                      /* to wykona się jeżeli zostanie zasygnalizowany event numer 1 */
                      /* na przykład jakieś obliczenia */
                   case 2:
                      /* to gdy zostanie zasygnalizowany event numer 2 */
                      /* na przykład wyświetlanie w oknie */
                      break;
                   default:
                      /* a to jak zostanie zasygnalizowany jakiś inny event, którego w tym programie
                      i tak nie ma, bo są tylko trzy */
                   }
                }
             } else {
                /* a tu coś co będzie wykonywane co czas ustalony zamiast INFINITE */
                /* Tak dla odmiany zostanie sklasyfikowane w zależności od numeru aktualnego wątku.
                zmienna data jest zmienną automatyczną, lokalną i unikalną dla każdego z wątków,
                bo przechowywana jest na stosie, a każdy wątek posiada własny stos,
                stąd istnieje możliwość takiego jej wykorzystania.
                Zmienne statyczne, globalne itp. są  wspólne dla wszystkich wątków
                współdzielących jeden fragment kodu */
                /* można to wykorzystać na przykład do wyłączania bumelujących wątków,
                jeżeli czas oczekiwania zostanie ustawiony na 10000, to można na przykład automatycznie
                kończyć wątki, które nie robią nic przez ostatnie 10 sekund, inna sprawa,
                że ponowne ich włączenie nie będzie już takie proste */
                switch(data) {
                case 0:
                   /* to wykona wątek o numerze 0 */
                   break;
                case 1:
                case 2:
                   /* to wątki o numerach 1 i 2 */
                   break;
                case 3:
                   /* tak dla przykładu wątek numer 3 może zasygnalizować jakiegoś eventa, na którego
                   oczekuje główna procedura */
                   /* to co tu jest nie zadziała, bo główny program nie czeka na żadne eventy,
                   a i takiego eventa nie ma zadeklarowanego (tylko przykład)
                   Jak już pisałem w głównym programie musiała by być pętla z funkcją
                   MsgWaitForMultipleObjects()
                   Koniecznie to Msg na poczatku, bo inaczej program nie doczeka się na wiadomości
                   przychodzące do okna. To samo z wątkami - każdy wątek, który tworzy okno
                   powiniwn używać MsgWaitForMultipleObjects() zamiast WaitForMultipleObjects()*/
                   /* SetEvent(jakisEventNaKtoryCzekaGlownyProgram); */
                default:
                   /* a to wykonają pozostałe wątki */
                }
             }
          }
       }
       _endthread();
    }



    /* Inicjalizacja tablicy zdarzeń */
    BOOL SetupEvents(void) {
    UINT i;

       /* Bardzo ważne - pierwszy event służy do sygnalizacji konieczności zakończenia wątków
       jego typ ustawiony jest na resetowany ręcznie, żeby nie zresetował go pierwszy wątek,
       ktory na niego zareaguje */
       rghEvent[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
       if (NULL == rghEvent[0]) return FALSE;

        for (i = 1; i < NEVENTS; i++)
        {
          /* Domyślnie tworzone jako automatyczne, niezasygnalizowane, odsyłam do dokumentacji */
            rghEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
            if (NULL == rghEvent[i]) return FALSE;
        }
        return TRUE;
    }


    /****************************************************/

    int WINAPI WinMain (HINSTANCE hThisInstance,
                        HINSTANCE hPrevInstance,
                        LPSTR lpszArgument,
                        int nFunsterStil)

    {
        MSG messages;            /* Here messages to the application are saved */
        WNDCLASSEX wincl;        /* Data structure for the windowclass */
       UINT wReturn;
       UINT i;

       if(SetupCreateEvents() == FALSE) {
            MessageBox(NULL, "CreateEvent() failed", "Error", MB_OK);
            return 1;
       }

       for(i = 0; i<NUMTHREADS; i++) {
          /* Jezeli chce sie uzywac funkcji z bibliotek C wewnatrz watkow,
          to nalezy uzyc tej funkcji zamiast CreateThread()
          Argumenty to odpowiednio: wskaznik na procedure,
             rozmiar stosu (zero oznacza domyslny, taki jak glownego programu,
             przekazywany do watku parametr */

    /* wartość zmiennej 'i' zostanie przekazana nowemu wątkowi jako argument 'data',
    czyli każdemu wątkowi zostanie przekazany jego numer, żeby mogły się rozróżnić (kod jest wspólny)
    jeżeli tworzy się kilka wątków nie współdzielących kodu, to taka konieczność nie istnieje
    Przekazywany parametr jest w zasadzie wskażnikiem, ale można i tak. */
          hjakisThread[i] = _beginthread(jakisThread, 0, (void *) i) ;      
          if(hjakisThread[i] == FALSE) {
             /* Nie powiodło się tworzenie wątku, w zasadzie powinno się utworzone do tej po wątki zabić,
             ale program i tak kończy pracę więc zrobi to za nas system */
             MessageBox(NULL, "CreateThread() failed", "Error", MB_OK);
             return 1;
          }
       }

       /* Wyzszy priorytet dla wszystkich wątków, tak dla przykładu. */
       for(i = 0; i<NUMTHREADS; i++) {
          SetThreadPriority(hjakisThread[i], THREAD_PRIORITY_ABOVE_NORMAL);
       }

        /* The Window structure */
        wincl.hInstance = hThisInstance;
        wincl.lpszClassName = szClassName;
        wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
        wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
        wincl.cbSize = sizeof (WNDCLASSEX);

        /* Use default icon and mouse-pointer */
        wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
        wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
        wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
        wincl.lpszMenuName = NULL;                 /* No menu */
        wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
        wincl.cbWndExtra = 0;                      /* structure or the window instance */
        /* Use Windows's default color as the background of the window */
        wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

        /* Register the window class, and if it fails quit the program */
        if (!RegisterClassEx (&wincl))
            return 1;

        /* The class is registered, let's create the program*/
        hwnd = CreateWindowEx (
               0,                   /* Extended possibilites for variation */
               szClassName,         /* Classname */
               "Okno zaczepno obronne",       /* Title Text */
               WS_OVERLAPPEDWINDOW, /* default window */
               CW_USEDEFAULT,       /* Windows decides the position */
               CW_USEDEFAULT,       /* where the window ends up on the screen */
               544,                 /* The programs width */
               375,                 /* and height in pixels */
               HWND_DESKTOP,        /* The window is a child-window to desktop */
               NULL,                /* No menu */
               hThisInstance,       /* Program Instance handler */
               NULL                 /* No Window Creation data */
               );

        /* Make the window visible on the screen */
        ShowWindow (hwnd, nFunsterStil);

    /* Można też zrobić tak, że wątek będzie sygnalizował różne zdarzenia głównemu programowi za pomocą eventów
    W tym celu jako warunku wykonywania głównej pętli należy użyć funkcji MsgWaitForMultipleObjects(),
    a wewnątrz niej zamiast GetMessage() funkcji PeekMessage() */

        while (GetMessage (&messages, NULL, 0, 0)) {
          TranslateMessage(&messages);
          DispatchMessage(&messages);
        }

       /* Jeżeli PC znalazł się tutaj, to znaczy że program kończy działanie */

       /* trzeba zasygnalizować wątkowi konieczność zakończenia działania */
       SetEvent(threadEvent[0]);

       /* Tę wartość trzeba ustawić na cokolwiek innego niż przedział podany niżej,
       który oznacza, że wątek zakończył działanie  */
       DWORD threadrc=WAIT_TIMEOUT;
       /* czekaj w petli az zakonczone zostana wszystki watki, co zostanie zasygnalizowane wartoscia
       z przedzialu WAIT_OBJECT_0 .. WAIT_OBJECT_0+NTHREADS-1 (włącznie) */
       while((threadrc < WAIT_OBJECT_0) && (threadrc > (WAIT_OBJECT_0+NTHREADS-1))) {
          /* Argumentami funkcji WaitForSingleObject(), WaitForMultipleObjects() i podobnych mogą być
          nie tylko wskaźniki na Eventy, ale i na wątki, semafory itp. patrz dokumentacja */
          /* Funkcja czeka az zostanie zasygnalizowane zakonczenie pracy przez wszystkie watki,
          ale nie dluzej niz 500ms */
          /* Przy takiej ilośći wątków dobrym pomysłem może być zwiększenie limitu czasu */
          threadrc = WaitForMultipleObjects(NTHREADS, hjakisThread, TRUE, 500);
          /* Jeżeli przekroczono czas oczekiwania */
          if(threadrc == WAIT_TIMEOUT) {
             if(MessageBox(NULL, "Can't finish thread (timeout).\nWait another 500ms?", "Error", MB_YESNO) == IDNO)
                /* Jak się kliknie na "Nie", to zawieszony wątek zostanie zignorowany i zabije go system
                Wątek można też zabić "ręcznie" za pomocą TerminateThread() chyba */
                 break;
          }
       }

       /* A tak się nieautomatycznego zasygnalizowanego eventa resetuje gdy juz zostanie "przetrawiony"
       chociaż akurat w tym wypadku nie jest to konieczne */
       ResetEvent(threadEvent[0]);


        /* The program return-value is 0 - The value that PostQuitMessage() gave */
        return messages.wParam;
    }


    /*  This function is called by the Windows function DispatchMessage()  */

    LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    PAINTSTRUCT ps;
    HDC hdc;


        switch (message)                  /* handle the messages */
        {
           case WM_CREATE:

          /* Tu są tworzone przyciski i inne duperelki */
          
          case WM_PAINT:

             hdc = BeginPaint (hwnd, &ps);
                /* Tu można coś narysować */
             EndPaint (hwnd, &ps);
               
             return 0;

            case WM_COMMAND:

             /* A tu się obsługuje przyciski itp. */
             /* Na przykład w ten sposób:
             /* if( LOWORD(wParam)==ID_PRZYCISKU && HIWORD(wParam)==BN_CLICKED) {
                SetEvent(threadEvent[0]);
             }
             Można zasygnalizować wątkowi konieczność zrobienia czegoś po naciśnięciu przycisku w oknie */
             /* A w ten sposób można zasygnalizować konieczność wykonania danej operacji
             wszystkim wątkom, które aktualnie oczekują na zdarzenie (czyli się obijają) */
             /* if( LOWORD(wParam)==ID_INNEGOPRZYCISKU && HIWORD(wParam)==BN_CLICKED) {
                PulseEvent(threadEvent[ileśtam]);
             }
             Warunkiem takiego zachowania jest wcześniejsze zainicjowanie eventa threadEvent[ileśtam], jako
             resetowanego ręcznie, czyli tak jak threadEvent[0].
             funkcja "wyzwoli" (w sensie: spowoduje zakończenie oczekiwania w funkcji *WaitFor*())
             wszystkie oczekujące wątki, które aktualnie "wyzwolić" można i ustawi stan eventa
             na zniezasynalizowany, pomimo tego, że zadeklarowany jest on jako resetowany ręcznie! */

    /* Called when CLOSE signal is sent to window (e.g. by pressing the 'X' button) */
          case WM_CLOSE:
             DestroyWindow(hwnd);
             return 0;

            case WM_DESTROY:
                PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
                break;

            default:
                return DefWindowProc (hwnd, message, wParam, lParam);
        }
        return 0;
    }
  • #5
    tomcio1-19
    Level 10  
    wielkie dzięki - będe nad tym pracował :)
  • #6
    tomcio1-19
    Level 10  
    hmm wywala mi błąd funkcjii 'void __cdecl' i nie wiem jak to uruchomic aby się kompilowalo - pomożesz może ??
  • #7
    shg
    Level 35  
    Poprawiłem literówki i kompiluje się normalnie.
    Problemem moze być to, że używasz Borlanda, dziwny ten kompilator jest.

    Pogrzebałem trochę i znalazłem informację dość istotną, wątki utworzone za pomocą funkcjai _beginthread() są do kitu, bo nie można na nie oczekiwać za pomocą funkcji *WaitFor*(), bo zakończenie takiego wątku powoduje automatyczne zwolnienie uchwytu, a jego przecież używa się do oczekiwania na zakończenie wątku. Zamiast tej funkcji należy użyć _beginthreadex(), która przypomina windowsową funkcję CreateThread(), ale z tą różnicą że w utworzonym takj wątku można używać funkcji ze standardowych bibliotek.
    Jeżeli dalej będziesz miał problemy, z deklaracjami to spróbuj zrobić to tak jak w pierwszym programie który wrzuciłem, czyli za pomocą windowsowych funkcji, warunek taki, że nie można standardowych funkcji języka C używać wewnątrz wątku, ale WinAPI udostępnia wiele funkcji, którymi można je zastąpić.

    Zauważ że funkcja, z której tworzone będą wątki zadeklarowana jest teraz w inny sposób (zwraca wartość). Poza tym w poprzednim programie był błąd, _beginthread() zwraca -1 w wypadku porażki, a sprawdzane było czy zwraca zero. _beginthreadex() zwraca zero jak CreateThread(). Wątki kończą działanie przez wywołanie _endthreadex(), a nie _endthread() jak poprzednio i zwracają wartość.

    Jeżeli będą jakieś problemy z __stdcall, to spróbuj zamiast tego dać __attribute__((__stdcall__)), chociaż nie wiem czy Borlandowski kompilator w ogóle coś takiego "zrozumie".

    Całkiem możliwe też, że trzeba będzie parametry kompilatora zmienić tak, żeby linkował program z bibliotekami przeznaczonymi do programów wielowątkowych (Mingw robi to automatycznie, MSVC wymaga parametru /MT, Borland nie wiem jak).

    Dorzuciłem też przykład z wykorzystaniem funkcji EnterCriticalSection() i EnterCriticalSection(). Poprawiłem przy okazji procedurę oczekiwania na zakończenie wszystkich wątków, był tam bezsensowny warunek porównania (typu wartość zmiennej jednocześnie mniejsza od np. 0 i większa od 50), którego o dziwo kompilator nie zgłosił, mimo że istniała możliwość wykrycia takiej sytuacji w trakcie kompilacji.

    Program skompilowałem i działa pod Mingw, powinien też działać bez zmian pod MSVC.

    W załączniku masz kod źródłowy i skompilowanego .exe

    A najważniejszego to nie napisałeś. Jaki błąd wywala kompilator?
  • #8
    tomcio1-19
    Level 10  
    Heh w pomocą kolegi doszliśmy do porozumienia z tymi 50-cioma wątkami - dwoma errorami:
    [C++ Warning] wielowatkowosc.cpp(108): W8066 Unreachable code;
    [C++ Warning] wielowatkowosc.cpp(314): W8004 'hdc' is assigned a value that is never used
    i na granicy zawieszenia kompa program sie kompiluje :) wywala jakieś okna mi szybko strasznie. Czyli działanie tego programu sprowadza się tylko do porównywania wartości funkcji ?? A oto poprawka:

    Code:
    #include <windows.h>
    
    #include <process.h>

    LRESULT CALLBACK WindowProcedure(HWND, UINT, WPARAM, LPARAM);

    /*  Make the class name into a global variable  */
    char szClassName[] = "Beat Detector";
    HWND hwnd;              /* Main window handle */

    #define NEVENTS (3)                     /* Ilość zdarzeń */
    #define NUMTHREADS (50)                  /* ilość wątków */
    HANDLE            threadEvent[NEVENTS];   /* tak na przykład ze trzy zdarzenia */

    HANDLE              hjakisThread[NUMTHREADS]; /* "uchwyty", dla każdego po jednym */

    /* do tworzenia wątku używana jest tym razem funkcja _beginthread(), stąd zamiast
       DWORD WINAPI jakisThread(LPVOID data)
    funkcja zadeklarowana jest jak poniżej*/
    void __cdecl jakisThread(void * data) {
    DWORD   eventn;

       while(1) {
          /* czeka na zasygnalizowanie któregoś z Eventów
          Zamiast INFINITE można podać czas oczekiwania w milisekundach,
          po którym funkcja zwraca wartość WAIT_TIMEOUT
          Jak wątek sobie tak czeka, to praktycznie nie zużywa czasu CPU */
          eventn = WaitForMultipleObjects(NEVENTS, threadEvent, FALSE, INFINITE);
          if(eventn != WAIT_FAILED) {         /* if there was no error */
             if(eventn != WAIT_TIMEOUT) {         /* jak nie uplynal czas, to znaczy ze zostalo cos zasygnalizowane  */
                eventn -= WAIT_OBJECT_0;        /* get number of event */

                if((eventn >= 0) && (eventn <= (NEVENTS-1))) {
                   /* eventn zawiera numer zasygnalizowanego zdarzenia
                   Ogolnie to takie zamieszanie lekkie to wprowadza, bo na zasygnalizowanego eventa
                   zareagować może dowolny z wątków, dlatego tu nastąpi
                   rozdział funkcji pomiędzy poszczególne wątki.
                   Pierwszy wątek, który się załapie sprawdzi sobie który event został zasygnalizowany
                   i wykona odpowiednią operację.
                   Zaletę ogromną ma to taką, że pozwala na przykład na zbalansowanie czasu CPU
                   między wieloma wątkami, bo gdy jeden z nich będzie zajęty na przykład obliczeniami,
                   sygnał odbierze inny, czekający wątek i zacznie wyświetlać wyniki tychże obliczeń,
                   a właściwie to nie tego co jest aktualnie liczone, a tego, co zostało wyliczone już wcześniej
                   i jest gotowe do wyświetlenia.
                   Można oczywiście (a nawet należy) bawić się w mutexy, semafory i sekcje krytyczne,
                   żeby zapewnić bezkolizyjny dostęp do danych, na przykład wątek wyświetlający wyniki
                   będzie musiał poczekać aż wątek wykonujący obliczenia zakończy działanie,
                   do tego celu służą między innymi funkcje EnterCriticalSection() i LeaveCriticalSection(),
                   pierwsza z nich czeka aż sekcja zostanie zwolniona, na przykład przez inny wątek,
                   który po zakończeniu operacji wywoła LeaveCriticalSection(). W jakiejś książce
                   spotkałem się z opisem, z którego wynikało, że kod pomiędzy Enter...() i Leave...()
                   wykonuje się bez przerwy, znaczy że na ten czas wyłączany jest multitasking, co jest
                   niestety (a może i na szczęście) bzdurą */
                   switch(eventn) {
                   case 0:   /* Event nr 0 używany jest do sygnalizowania konieczności zakończenia wątku  */
                      _endthread();            /* Zakończ wątek */
                      break;
                   case 1:
                      /* to wykona się jeżeli zostanie zasygnalizowany event numer 1 */
                      /* na przykład jakieś obliczenia */
                   case 2:
                      /* to gdy zostanie zasygnalizowany event numer 2 */
                      /* na przykład wyświetlanie w oknie */
                      break;
                   default:
                      /* a to jak zostanie zasygnalizowany jakiś inny event, którego w tym programie
                      i tak nie ma, bo są tylko trzy */
                      break;
                   }
                }
             } else {
                /* a tu coś co będzie wykonywane co czas ustalony zamiast INFINITE */
                /* Tak dla odmiany zostanie sklasyfikowane w zależności od numeru aktualnego wątku.
                zmienna data jest zmienną automatyczną, lokalną i unikalną dla każdego z wątków,
                bo przechowywana jest na stosie, a każdy wątek posiada własny stos,
                stąd istnieje możliwość takiego jej wykorzystania.
                Zmienne statyczne, globalne itp. są  wspólne dla wszystkich wątków
                współdzielących jeden fragment kodu */
                /* można to wykorzystać na przykład do wyłączania bumelujących wątków,
                jeżeli czas oczekiwania zostanie ustawiony na 10000, to można na przykład automatycznie
                kończyć wątki, które nie robią nic przez ostatnie 10 sekund, inna sprawa,
                że ponowne ich włączenie nie będzie już takie proste */
                switch((int)data) {
                case 0:
                   /* to wykona wątek o numerze 0 */
                   break;
                case 1:
                case 2:
                   /* to wątki o numerach 1 i 2 */
                   break;
                case 3:
                   /* tak dla przykładu wątek numer 3 może zasygnalizować jakiegoś eventa, na którego
                   oczekuje główna procedura */
                   /* to co tu jest nie zadziała, bo główny program nie czeka na żadne eventy,
                   a i takiego eventa nie ma zadeklarowanego (tylko przykład)
                   Jak już pisałem w głównym programie musiała by być pętla z funkcją
                   MsgWaitForMultipleObjects()
                   Koniecznie to Msg na poczatku, bo inaczej program nie doczeka się na wiadomości
                   przychodzące do okna. To samo z wątkami - każdy wątek, który tworzy okno
                   powiniwn używać MsgWaitForMultipleObjects() zamiast WaitForMultipleObjects()*/
                   /* SetEvent(jakisEventNaKtoryCzekaGlownyProgram); */
                default:
                   break;
                   /* a to wykonają pozostałe wątki */
                }
             }
          }
       }
       _endthread();
    }



    /* Inicjalizacja tablicy zdarzeń */
    BOOL SetupEvents(void) {
    UINT i;
         VOID* rghEvent[NEVENTS];
       /* Bardzo ważne - pierwszy event służy do sygnalizacji konieczności zakończenia wątków
       jego typ ustawiony jest na resetowany ręcznie, żeby nie zresetował go pierwszy wątek,
       ktory na niego zareaguje */
       rghEvent[0] = CreateEvent(NULL, TRUE, FALSE, NULL);
       if (NULL == rghEvent[0]) return FALSE;

        for (i = 1; i < NEVENTS; i++)
        {
          /* Domyślnie tworzone jako automatyczne, niezasygnalizowane, odsyłam do dokumentacji */
            rghEvent[i] = CreateEvent(NULL, FALSE, FALSE, NULL);
            if (NULL == rghEvent[i]) return FALSE;
        }
        return TRUE;
    }


    /****************************************************/

    int WINAPI WinMain (HINSTANCE hThisInstance,
                        HINSTANCE hPrevInstance,
                        LPSTR lpszArgument,
                        int nFunsterStil)

    {
        MSG messages;            /* Here messages to the application are saved */
        WNDCLASSEX wincl;        /* Data structure for the windowclass */
       UINT wReturn;
       int i;

      // if(SetupCreateEvents() == FALSE) {
      //      MessageBox(NULL, "CreateEvent() failed", "Error", MB_OK);
       //     return 1;
       //}

       for(i = 0; i<NUMTHREADS; i++) {
          /* Jezeli chce sie uzywac funkcji z bibliotek C wewnatrz watkow,
          to nalezy uzyc tej funkcji zamiast CreateThread()
          Argumenty to odpowiednio: wskaznik na procedure,
             rozmiar stosu (zero oznacza domyslny, taki jak glownego programu,
             przekazywany do watku parametr */
    void * u = (void*)i;
    /* wartość zmiennej 'i' zostanie przekazana nowemu wątkowi jako argument 'data',
    czyli każdemu wątkowi zostanie przekazany jego numer, żeby mogły się rozróżnić (kod jest wspólny)
    jeżeli tworzy się kilka wątków nie współdzielących kodu, to taka konieczność nie istnieje
    Przekazywany parametr jest w zasadzie wskażnikiem, ale można i tak. */
          hjakisThread[i] = (void*)_beginthread(jakisThread, 0,  u) ;     
          if(hjakisThread[i] == FALSE) {
             /* Nie powiodło się tworzenie wątku, w zasadzie powinno się utworzone do tej po wątki zabić,
             ale program i tak kończy pracę więc zrobi to za nas system */
             MessageBox(NULL, "CreateThread() failed", "Error", MB_OK);
             return 1;
          }
       }

       /* Wyzszy priorytet dla wszystkich wątków, tak dla przykładu. */
       for(i = 0; i<NUMTHREADS; i++) {
          SetThreadPriority(hjakisThread[i], THREAD_PRIORITY_ABOVE_NORMAL);
       }

        /* The Window structure */
        wincl.hInstance = hThisInstance;
        wincl.lpszClassName = szClassName;
        wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
        wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
        wincl.cbSize = sizeof (WNDCLASSEX);

        /* Use default icon and mouse-pointer */
        wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
        wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
        wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
        wincl.lpszMenuName = NULL;                 /* No menu */
        wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
        wincl.cbWndExtra = 0;                      /* structure or the window instance */
        /* Use Windows's default color as the background of the window */
        wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

        /* Register the window class, and if it fails quit the program */
        if (!RegisterClassEx (&wincl))
            return 1;

        /* The class is registered, let's create the program*/
        hwnd = CreateWindowEx (
               0,                   /* Extended possibilites for variation */
               szClassName,         /* Classname */
               "Okno zaczepno obronne",       /* Title Text */
               WS_OVERLAPPEDWINDOW, /* default window */
               CW_USEDEFAULT,       /* Windows decides the position */
               CW_USEDEFAULT,       /* where the window ends up on the screen */
               544,                 /* The programs width */
               375,                 /* and height in pixels */
               HWND_DESKTOP,        /* The window is a child-window to desktop */
               NULL,                /* No menu */
               hThisInstance,       /* Program Instance handler */
               NULL                 /* No Window Creation data */
               );

        /* Make the window visible on the screen */
        ShowWindow (hwnd, nFunsterStil);

    /* Można też zrobić tak, że wątek będzie sygnalizował różne zdarzenia głównemu programowi za pomocą eventów
    W tym celu jako warunku wykonywania głównej pętli należy użyć funkcji MsgWaitForMultipleObjects(),
    a wewnątrz niej zamiast GetMessage() funkcji PeekMessage() */

        while (GetMessage (&messages, NULL, 0, 0)) {
          TranslateMessage(&messages);
          DispatchMessage(&messages);
        }

       /* Jeżeli PC znalazł się tutaj, to znaczy że program kończy działanie */

       /* trzeba zasygnalizować wątkowi konieczność zakończenia działania */
       SetEvent(threadEvent[0]);

       /* Tę wartość trzeba ustawić na cokolwiek innego niż przedział podany niżej,
       który oznacza, że wątek zakończył działanie  */
       DWORD threadrc=WAIT_TIMEOUT;
       /* czekaj w petli az zakonczone zostana wszystki watki, co zostanie zasygnalizowane wartoscia
       z przedzialu WAIT_OBJECT_0 .. WAIT_OBJECT_0+NTHREADS-1 (włącznie) */
       while((threadrc < WAIT_OBJECT_0) && (threadrc > (WAIT_OBJECT_0+NUMTHREADS-1))) {
          /* Argumentami funkcji WaitForSingleObject(), WaitForMultipleObjects() i podobnych mogą być
          nie tylko wskaźniki na Eventy, ale i na wątki, semafory itp. patrz dokumentacja */
          /* Funkcja czeka az zostanie zasygnalizowane zakonczenie pracy przez wszystkie watki,
          ale nie dluzej niz 500ms */
          /* Przy takiej ilośći wątków dobrym pomysłem może być zwiększenie limitu czasu */
          threadrc = WaitForMultipleObjects(NUMTHREADS, hjakisThread, TRUE, 500);
          /* Jeżeli przekroczono czas oczekiwania */
          if(threadrc == WAIT_TIMEOUT) {
             if(MessageBox(NULL, "Can't finish thread (timeout).\nWait another 500ms?", "Error", MB_YESNO) == IDNO)
                /* Jak się kliknie na "Nie", to zawieszony wątek zostanie zignorowany i zabije go system
                Wątek można też zabić "ręcznie" za pomocą TerminateThread() chyba */
                 break;
          }
       }

       /* A tak się nieautomatycznego zasygnalizowanego eventa resetuje gdy juz zostanie "przetrawiony"
       chociaż akurat w tym wypadku nie jest to konieczne */
       ResetEvent(threadEvent[0]);


        /* The program return-value is 0 - The value that PostQuitMessage() gave */
        return messages.wParam;
    }


    /*  This function is called by the Windows function DispatchMessage()  */

    LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
    PAINTSTRUCT ps;
    HDC hdc;


        switch (message)                  /* handle the messages */
        {
           case WM_CREATE:

          /* Tu są tworzone przyciski i inne duperelki */
         
          case WM_PAINT:

             hdc = BeginPaint (hwnd, &ps);
                /* Tu można coś narysować */
             EndPaint (hwnd, &ps);
               
             return 0;

            case WM_COMMAND:

             /* A tu się obsługuje przyciski itp. */
             /* Na przykład w ten sposób:
             /* if( LOWORD(wParam)==ID_PRZYCISKU && HIWORD(wParam)==BN_CLICKED) {
                SetEvent(threadEvent[0]);
             }
             Można zasygnalizować wątkowi konieczność zrobienia czegoś po naciśnięciu przycisku w oknie */
             /* A w ten sposób można zasygnalizować konieczność wykonania danej operacji
             wszystkim wątkom, które aktualnie oczekują na zdarzenie (czyli się obijają) */
             /* if( LOWORD(wParam)==ID_INNEGOPRZYCISKU && HIWORD(wParam)==BN_CLICKED) {
                PulseEvent(threadEvent[ileśtam]);
             }
             Warunkiem takiego zachowania jest wcześniejsze zainicjowanie eventa threadEvent[ileśtam], jako
             resetowanego ręcznie, czyli tak jak threadEvent[0].
             funkcja "wyzwoli" (w sensie: spowoduje zakończenie oczekiwania w funkcji *WaitFor*())
             wszystkie oczekujące wątki, które aktualnie "wyzwolić" można i ustawi stan eventa
             na zniezasynalizowany, pomimo tego, że zadeklarowany jest on jako resetowany ręcznie! */

    /* Called when CLOSE signal is sent to window (e.g. by pressing the 'X' button) */
          case WM_CLOSE:
             DestroyWindow(hwnd);
             return 0;

            case WM_DESTROY:
                PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
                break;

            default:
                return DefWindowProc (hwnd, message, wParam, lParam);
        }
        return 0;
    }