使用什么而不是TThread.Suspend()

时间:2018-03-17 18:09:20

标签: multithreading c++builder

假设我有一个工作线程填充主线程中声明的大型向量。当工作线程仍在运行时(响应用户交互),我希望主线程检查向量是否已填充到一定大小。如果有,我希望它从矢量中提取一些值。如果它不是我想让它等到工作线程填充到所需的大小。

由于工作线程仍然可以向向量添加项目(可能导致调整大小/移动)我认为我只能在工作线程被挂起但不推荐使用TThread.Suspend()时执行此操作。我花了几天时间看着TMutex,TSemaphore等,但文档很糟糕。有人能指出我正确的方向吗?

一种可能的解决方案是在工作线程中填充一个单独的较小向量,然后使用synchronize将其附加到大向量(等等),但我想避免这种情况。

2 个答案:

答案 0 :(得分:0)

  

由于工作线程仍然可以向向量添加项目(可能导致调整大小/移动)我认为我只能在工作线程被挂起时执行此操作

这是一个非常好的主意。

  

但不推荐使用TThread.Suspend()。

即使它没有被弃用,使用起来仍然很危险。只有调试器应该挂起一个线程,这就是SuspendThread() API的用途。

  

我花了几天时间看着TMutex,TSemaphore等,但文档很糟糕。有人能指出我正确的方向吗?

您可以使用TCriticalSectionTMutex简单地包含对向量的所有访问权限,然后主要和工作线程都可以在需要对向量执行任何操作时进入锁定。例如:

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;

如果您发现工作线程比主线程更多地访问向量,您可以考虑改为使用TMultiReadExclusiveWriteSynchronizerSRW lock

或者,您可以使用一些TEvent个对象来通知工作线程何时暂停以及何时恢复。然后主线程可以通知线程暂停并等待它实际暂停,然后访问向量并在完成时取消线程。例如:

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;

答案 1 :(得分:0)

我的努力如下。它避免了Remy的TEvent的复杂性以及J ..最后评论所指出的TCriticalSection的陷阱。这是假设它有效。它似乎确实如此,但如果有人能够找到我可能陷入的陷阱,我将感激不尽。

向用户呈现包含名为VecNdx的TEdit的TForm,用户用来输入他想要的矢量值的索引和一个名为GetVecVal的TButton,点击时,通过在TLabel中打印VecNdx的矢量值来响应叫做VecVal。

虽然矢量值本身是由rand()函数生成的,但您可以将它们视为逐步执行查询结果集的结果,其中直到最后一步之后才知道该大小。

.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);
    }
}
//---------------------------------------------------------------------------