0

Suppose I have a worker thread that populates a large vector declared in the main thread. While the worker thread is still running (in response to user interaction) I want the main thread to check if the vector has been populated up to a certain size. If it has I want it to extract some values from the vector. If it hasn't i want it to wait till the worker thread populates up to the required size.

As the worker thread could still be adding items to the vector (possibly resulting in a resize/move) I'm thinking I can only do this while the worker thread is suspended but TThread.Suspend() is deprecated. I've spent days looking at TMutex, TSemaphore etc. but the documentation is dire. Could anyone point me in the right direction?

One possible solution is to populate a separate smaller vector in the worker thread and then use synchronize to append that to the large vector (and so on) but I'd like to avoid that.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
NoComprende
  • 731
  • 4
  • 14
  • Is the "certain size" known before the thread is started? – MartynA Mar 17 '18 at 18:12
  • No Martin. The ultimate size of the vector isn't known. The main thread can be considered as being asked by the user to return the vector at a random index. – NoComprende Mar 17 '18 at 18:20
  • J.., how would that help? Would I not still have to suspend the worker thread to access the vector? – NoComprende Mar 17 '18 at 18:25
  • J.., but the main thread could be retrieving Vec[10] while the worker thread was doing a push_back that resulted in Vec[10] 's address changing. – NoComprende Mar 17 '18 at 18:33
  • I think there are too many unknowns here to make any good suggestions. How best to synchronize depends on details of what you are doing that we don't know. You don't need a semaphore - probably just a critical section would do. Having the worker notify the main thread of a milestone is preferable to the main thread checking. If you really want the worker to stop then a synchronized event would not even require a critical section - the worker will naturally block until the handler completes. – J... Mar 17 '18 at 19:09
  • I'm using c++ builder Tokyo but I'm assuming the answer would be the same in Delphi. The problem as stated above describes pretty much what i want to do. I haven't come across 'synchronised event'. I will look it up. Thanks. – NoComprende Mar 17 '18 at 19:28
  • @NoComprende By event I mean simply a callback, execute it using `TThread.Synchronize`. – J... Mar 17 '18 at 23:46
  • J.., I realised that shortly after I posted that last comment. At the time I was looking at TEvent and became confused. – NoComprende Mar 18 '18 at 08:13
  • 1
    What's the -1 all about? I don't think I could've put the question any simpler or clearer than I did. – NoComprende Mar 18 '18 at 08:18

2 Answers2

1

As the worker thread could still be adding items to the vector (possibly resulting in a resize/move) I'm thinking I can only do this while the worker thread is suspended

That is a very good idea.

but TThread.Suspend() is deprecated.

Even when it was not deprecated, it was still dangerous to use. Only a debugger should ever suspend a thread, that is what the SuspendThread() API is intended for.

I've spent days looking at TMutex, TSemaphore etc. but the documentation is dire. Could anyone point me in the right direction?

You could simply wrap all access to the vector with a TCriticalSection or TMutex, then the main and worker threads can both enter the lock whenever they need to do anything with the vector. For example:

type
  TMyThread = class(TThread)
  private
    FLock: TCriticalSection;
  protected
    procedure Execute; override;
  public
    constructor Create; reintroduce;
    destructor Destroy; override;
    procedure Lock;
    procedure Unlock;
  end;

constructor TMyThread.Create;
begin
  inherited Create(False);
  FLock := TCriticalSection.Create;
end;

destructor TMyThread.Destroy;
begin
  FLock.Free;
end;

procedure TMyThread.Lock;
begin
  FLock.Enter;
end;

procedure TMyThread.Unlock;
begin
  FLock.Leave;
end;

procedure TMyThread.Execute;
begin
  while not Terminated do
  begin
    Lock;
    try
      // do something...
    finally
      Unlock;
    end;
  end;
end;

MyThread.Lock;
try
  if Vector.Size >= X then
  begin
    // do something ...
  end;
finally
  MyThread.Unlock;
end;

If you find that the worker thread accesses the vector more than the main thread does, you might consider using a TMultiReadExclusiveWriteSynchronizer or a SRW lock instead.

Or, you could use some TEvent objects to signal the worker thread when to pause and when to resume. The main thread could then signal the thread to pause and wait for it to actually pause, then access the vector and unpause the thread when done. For example:

type
  TMyThread = class(TThread)
  private
    FPauseEvent: TEvent;
    FPausedEvent: TEvent;
    FResumeEvent: TEvent;
    procedure CheckForPause;
  protected
    procedure Execute; override;
  public
    constructor Create; reintroduce;
    destructor Destroy; override;
    procedure Pause;
    procedure Unpause;
  end;

constructor TMyThread.Create;
begin
  inherited Create(False);
  FPauseEvent := TEvent.Create(nil, True, False, '');
  FPausedEvent := TEvent.Create(nil, True, False, '');
  FResumeEvent := TEvent.Create(nil, True, True, '');
end;

destructor TMyThread.Destroy;
begin
  FPauseEvent.Free;
  FPausedEvent.Free;
  FResumeEvent.Free;
end;

procedure TMyThread.Pause;
begin
  FResumeEvent.ResetEvent;
  FPauseEvent.SetEvent;
  FPausedEvent.WaitFor(Infinite);
end;

procedure TMyThread.Unpause;
begin
  FPauseEvent.ResetEvent;
  FResumeEvent.SetEvent;
end;

procedure TMyThread.CheckForPause;
begin
  if FPauseEvent.WaitFor(0) = wrSignaled then
  begin
    FPausedEvent.SetEvent;
    FResumeEvent.WaitFor(Infinite);
    FPausedEvent.ResetEvent;
  end;
end;

procedure TMyThread.Execute;
begin
  while not Terminated do
  begin
    CheckForPause;
    if Terminated then Exit;
    // do something...
  end;
end;

MyThread.Pause;
try
  if Vector.Size >= X then
  begin
    // do something ...
  end;
finally
  MyThread.Unpause;
end;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 1
    With critical sections in this pattern, one must be careful about : [this situation.](https://stackoverflow.com/q/47550377/327083) – J... Mar 19 '18 at 11:15
  • Remy, thanks very much for that reply. It contains a lot I hadn't come across but I'll need some time to experiment with it. – NoComprende Mar 20 '18 at 16:08
1

My effort below. It avoids the complexity of Remy's TEvent and the pitfalls of TCriticalSection pointed to by J..'s last comment. That's assuming it works. It does appear to but I'd be grateful if anyone could have a look for traps I may have fell into.

The user is presented with a TForm containing a TEdit called VecNdx which the user uses to enter the index he wants the vector value for and a TButton called GetVecVal which, when clicked, responds by printing the vector value for VecNdx in a TLabel called VecVal.

While the vector values themselves are generated by the rand() function you can think of them as being the results from stepping through a query result set where the size isn't known till after the last step.

.h file

#ifndef ThreadH
#define ThreadH
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <Vcl.Controls.hpp>
#include <Vcl.StdCtrls.hpp>
#include <Vcl.Forms.hpp>
#include <Vcl.ComCtrls.hpp>
#include <vector>
#include <atomic>
//---------------------------------------------------------------------------
class TMainForm : public TForm
{
__published:    // IDE-managed Components
    TEdit *VecNdx; // user enters vector index
    TButton *GetVecVal; // retreives value for vector at index entered above
    TLabel *VecVal; // displays above value
    void __fastcall GetVecValClick(TObject *Sender);

private:    // User declarations
    class TPopulate : public TThread
    {
    private:
        TMainForm *Main;
        void __fastcall ShowPopulated(void);
        int Count;
        clock_t ThreadStart; // clock() when thread starts running
    protected:
        void __fastcall Execute();
    public:
        __fastcall TPopulate(TMainForm *Parent) : Main(Parent) {}
    } *Populate;

    int VecSize=-1; // updated only after Populate finishes
    std::vector<int> Vec;
    std::atomic<int> UserNdx=-1,UserVal,CountSoFar;

public:     // User declarations
    __fastcall TMainForm(TComponent* Owner);
    __fastcall ~TMainForm();
};
//---------------------------------------------------------------------------
extern PACKAGE TMainForm *MainForm;
//---------------------------------------------------------------------------
#endif





.cpp file

#include <vcl.h>
#pragma hdrstop

#include "Thread.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMainForm *MainForm;
//---------------------------------------------------------------------------
__fastcall TMainForm::TMainForm(TComponent* Owner) : TForm(Owner)
{
    Populate=new TPopulate(this);
}
//---------------------------------------------------------------------------
__fastcall TMainForm::~TMainForm()
{
    delete Populate;
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::TPopulate::ShowPopulated(void)
{
    Main->Caption = (Terminated ? String("Terminated after ") : String(Count)+" values in ")
    +((clock()-ThreadStart)/CLOCKS_PER_SEC)+" secs";
    Main->VecSize=Count;
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::TPopulate::Execute()
{
    ThreadStart=clock();
    const int Mx=100000000;
    Count=0;
    for (int u; !Terminated && Count<Mx;)
    {
        Main->Vec.push_back(rand() % Mx);
        Count++;
        if ((u = Main->UserNdx) != -1)
        {
            if (Count>u) Main->UserVal=Main->Vec[u];
            else Main->CountSoFar=Count;
            Main->UserNdx=-1;
        }
    }
    Synchronize(ShowPopulated);
}
//---------------------------------------------------------------------------
void __fastcall TMainForm::GetVecValClick(TObject *Sender)
{
    int Ndx=VecNdx->Text.ToIntDef(-1);
    if (Ndx<0 || (VecSize>=0 && Ndx>=VecSize)) throw Exception("Range Error");
    if (VecSize>=0) VecVal->Caption=Vec[Ndx];
    else
    {
        CountSoFar=0; // if Populate changes CountSoFar => Vec[UserNdx] not yet assigned
        UserNdx=Ndx;
        while (UserNdx!=-1); // Ensure Populate processes UserNdx
        VecVal->Caption = CountSoFar ? "Populated only to "+String(CountSoFar) : int(UserVal);
    }
}
//---------------------------------------------------------------------------
NoComprende
  • 731
  • 4
  • 14