26. Používání vláken II
  1. Když již máme implementovány třídy vláken metodou Execute, můžeme je použít v naší aplikaci spuštěním kódu v metodě Execute. K použití vlákna, nejprve vytvoříme instanci třídy vlákna. Instanci vlákna můžeme vytvořit tak, že je spuštěno bezprostředně nebo jej vytvoříme v klidovém stavu, které později spustíme voláním metody Resume. Pro vytvoření vlákna s bezprostředním spuštěním předáme konstruktoru parametr false. Např. následující řádek vytváří vlákno a spouští jeho provádění:

  2. TMyThread *SecondProcess = new TMyThread(false); // vytvoření a spuštění
    Nevytvářejte ve své aplikaci mnoho vláken. Doporučený limit je 16 vláken na proces v jednoprocesorovém systému. Tento limit předpokládá, že většina těchto vláken bude čekat na externí události. Pokud všechna vlákna jsou aktivní, pak jich je možno použít méně.
    Můžeme vytvořit více instancí stejného typu vlákna pro provádění paralelního kódu. Např. můžeme vytvořit novou instanci vlákna v reakci na nějakou akci uživatele umožňující každému vláknu zpracovávat očekávanou reakci.
    Množství času CPU věnované vláknu je určeno nastavením priority v konstruktoru. Můžeme také vytvořit vlákno v klidovém stavu a nastavit prioritu až před jeho spuštěním:
    TMyThread *SecondProcess = new TMyThread(true);// vytvoření bez spuštění
    SecondProcess->Priority = tpLower; // nastavení priority
    SecondProcess->Resume();           // spuštění vlákna
    Vlákna mohou být spouštěna a zastavována několikrát před dokončením provádění. Zastavení vlákna je dočasné a provedeme jej voláním metody Suspend. Pro opětovné spuštění voláme jeho metodu Resume. Suspend inkrementuje interní čítač, a tak můžeme vnořovat volání Suspend a Resume. Vlákno nepokračuje v provádění dokud všechny Suspend nemají odpovídající Resume.
    K trvalému ukončení vlákna voláme metodu Terminate. Tato metoda nastavuje vlastnost Terminated vlákna na true. Pokud metoda Execute je správně implementována, je periodicky testována vlastnost Terminated a ukončuje provádění, když je true.
    Když naše aplikace vyžaduje více instancí stejné třídy vlákna (pro opakované spouštění), pak můžeme zvýšit výkonnost odkládáním vláken pro opětovné použití (namísto jejich rušení a opětovného vytváření). Pro odkládání vláken, musíme udržovat seznam vytvořených vláken. Tento seznam může být udržován objektem, který používá vlákna nebo můžeme použít globální proměnnou k uložení odloženého vlákna.
    Když je požadováno nové vlákno, pak je použito odložené vlákno nebo vytvořeno nové vlákno (viz následující funkce):
    TThreadList *pCache;
    ...
    TThread *GetThread(void)
    {
      TList *pList = pCache->LockList();
      TThread *NewThread;
      if (pList->Count) // žádné vlákno není odloženo
      {
        NewThread = (TThread *)pList->Items[0];
        pList->Delete(0); // odstranění ze seznamu
      }
      else
        NewThread=(TThread *)new TMyThread(true);//vytvoření ale nespuštění
      pCache->UnlockList();
      return NewThread;
    }
    Jestliže vlákno končí provádění je odloženo (obsluha OnTerminate):
    void __fastcall TMyThread::Terminate(TObject *Sender)
    {
      pCache->Add(&this);
    }
    Když aplikace končí provádění (nebo objekt, který vlastní vlákna je rušen), pak odložená vlákna musí být uvolněna (obsluha OnDeactivate aplikace):
    void __fastcall TDataModule1::ApplicationDeactivate(TObject *Sender)
    {
      TList *pList = pCache->LockList();
      for (int i = pList->Count - 1; i >= 0; i--)
      {
        delete pList->Items[i];
        pList->Delete(i);
      }
      pCache->UnlockList();
    }
  3. Pokud ladíme vícevláknové aplikace, pak můžeme sledovat stav všech současně prováděných vláken nebo určit které vlákno bude prováděno, když provádění je zastaveno bodem přerušení. Můžeme použít dialogové okno Stavu vláken k manipulaci s vlákny v naší aplikaci. Toto okno zobrazíme volbou View | Threads. Když nastane ladící událost (bod přerušení, provádění) pak zobrazení stavu vlákna indikuje stav každého vlákna. V místní nabídce okna jsou volby k lokalizaci odpovídajícího zdroje nebo k udělání jiného vlákna aktuálním. Jestliže vlákno je označeno jako aktuální, pak následující krok nebo operace je relativní k tomuto vláknu. Vlákna jsou identifikována identifikačními čísly (hodnota vlastnosti ThreadID).
  4. Vrátíme se k naší aplikaci z předchozí kapitoly. Formulář aplikace zvětšíme a nastavíme u něj tyto vlastnosti: BorderStyle na bsDialog a Caption na Demonstrace řazení. Na formulář přidáme tři komponenty Bevel a nastavíme u nich vlastnosti Top na 24, Width na 177 a Height na 233. U první z nich nastavíme Left na 8, u druhé na 192 a třetí na 376. Do každé z těchto komponent vložíme komponentu PaintBox a nastavíme u nich stejné hodnoty vlastností jako u komponent Bevel. Levou z těchto komponent nazveme BubbleSortBox, prostřední SelectionSortBox a pravou QuickSortBox. Nad každou z těchto komponent vložíme Label s texty: Bublinková metoda, Řazení výběrem a Quick Sort. Do spodní části formuláře vložíme ještě tlačítko s textem Začni řadit. Tím je tento formulář hotov. Do deklarace formuláře vložíme další deklarace (viz následující výpis hlavičkového souboru formuláře):

  5. #ifndef ThSortH
    #define ThSortH
    #include <StdCtrls.hpp>
    #include <ExtCtrls.hpp>
    #include <Dialogs.hpp>
    #include <Forms.hpp>
    #include <Controls.hpp>
    #include <Graphics.hpp>
    #include <Classes.hpp>
    #include <SysUtils.hpp>
    #include <Messages.hpp>
    #include <Windows.hpp>
    #include <System.hpp>
    class TThreadSortForm : public TForm
    {
    __published:
     TButton *StartBtn;
     TPaintBox *BubbleSortBox;
     TPaintBox *SelectionSortBox;
     TPaintBox *QuickSortBox;
     TLabel *Label1;
     TBevel *Bevel1;
     TBevel *Bevel2;
     TBevel *Bevel3;
     TLabel *Label2;
     TLabel *Label3;
     void __fastcall BubbleSortBoxPaint(TObject *Sender);
     void __fastcall SelectionSortBoxPaint(TObject *Sender);
     void __fastcall QuickSortBoxPaint(TObject *Sender);
     void __fastcall FormCreate(TObject *Sender);
     void __fastcall StartBtnClick(TObject *Sender);
    private:
     int ThreadsRunning;
     void __fastcall RandomizeArrays(void);
     void __fastcall ThreadDone(TObject *Sender);
    public:
     void __fastcall PaintArray(TPaintBox *Box,const int *A,
          const int A_Size);
     virtual __fastcall TThreadSortForm(TComponent *Owner);
    };
    //--------------------------------------------------------------------
    typedef int TSortArray[115];
    typedef TSortArray *PSortArray;
    //--------------------------------------------------------------------
    extern PACKAGE TThreadSortForm *ThreadSortForm;
    extern bool ArraysRandom;
    extern int BubbleSortArray[115];
    extern int SelectionSortArray[115];
    extern int QuickSortArray[115];
    #endif
    Následuje výpis jednotky formuláře:
    #include <vcl.h>
    #pragma hdrstop
    #include <stdlib.h>
    #include "thsort.h"
    #include "sortthd.h"
    #pragma resource "*.dfm"
    TThreadSortForm *ThreadSortForm;
    //--------------------------------------------------------------
    Boolean ArraysRandom;
    TSortArray BubbleSortArray, SelectionSortArray, QuickSortArray;
    __fastcall TThreadSortForm::TThreadSortForm(TComponent *Owner)
      : TForm(Owner)
    {
    }
    void __fastcall TThreadSortForm::PaintArray(TPaintBox *Box,
                    int const *A, int const ASize)
    {
      int i;
      TCanvas *canvas;
      canvas = Box->Canvas;
      canvas->Pen->Color = clRed;
      for (i=0; i < ASize; i++)
        PaintLine(canvas, i, A[i]);
    }
    //---------------------------------------------------------------
    void __fastcall TThreadSortForm::BubbleSortBoxPaint(TObject *)
    {
      PaintArray(BubbleSortBox, EXISTINGARRAY(BubbleSortArray));
    }
    void __fastcall TThreadSortForm::SelectionSortBoxPaint(TObject *)
    {
      PaintArray(SelectionSortBox, EXISTINGARRAY(SelectionSortArray));
    }
    void __fastcall TThreadSortForm::QuickSortBoxPaint(TObject * /*Sender*/)
    {
      PaintArray(QuickSortBox, EXISTINGARRAY(QuickSortArray));
    }
    //---------------------------------------------------------------
    void __fastcall TThreadSortForm::FormCreate(TObject * /*Sender*/)
    {
      RandomizeArrays();
    }
    void __fastcall TThreadSortForm::StartBtnClick(TObject * /*Sender*/)
    {
      TBubbleSort *bubble;
      TSelectionSort *selsort;
      TQuickSort *qsort;
      RandomizeArrays();
      ThreadsRunning = 3;
      bubble = new TBubbleSort(BubbleSortBox,
                               EXISTINGARRAY(BubbleSortArray));
      bubble->OnTerminate = ThreadDone;
      selsort = new TSelectionSort(SelectionSortBox,
                                   EXISTINGARRAY(SelectionSortArray));
      selsort->OnTerminate = ThreadDone;
      qsort = new TQuickSort(QuickSortBox, EXISTINGARRAY(QuickSortArray));
      qsort->OnTerminate = ThreadDone;
      StartBtn->Enabled = False;
    }
    //---------------------------------------------------------------------
    void __fastcall TThreadSortForm::RandomizeArrays()
    {
      int i;
      if (! ArraysRandom)
      {
        Randomize();
        for (i=0; i < ARRAYSIZE(BubbleSortArray); i++)
          BubbleSortArray[i] = random(170);
        memcpy(SelectionSortArray, BubbleSortArray,
               sizeof(SelectionSortArray));
        memcpy(QuickSortArray, BubbleSortArray, sizeof(QuickSortArray));
        ArraysRandom = True;
        Repaint();
      }
    }
    //---------------------------------------------------------------------
    void __fastcall TThreadSortForm::ThreadDone(TObject * /*Sender*/)
    {
      ThreadsRunning--;
      if (! ThreadsRunning)
      {
        StartBtn->Enabled = True;
        StartBtn->SetFocus();
        ArraysRandom = False;
      }
    }
    Pokuste se podle tohoto výpisu vytvořit běžící aplikaci a snažte se pochopit jednotlivé činnosti. Snažte se pochopit jak pracují vícevláknové aplikace.
  6. Přidejte do předchozí aplikace demonstraci dalšího způsobu řazení (vlákno, které jste vytvořili na závěr předchozí kapitoly).
26. Používání vláken II