使用什么代替 TThread.Suspend()
What to use instead of TThread.Suspend()
假设我有一个工作线程填充在主线程中声明的一个大向量。虽然工作线程仍然是 运行(响应用户交互),但我希望主线程检查向量是否已填充到一定大小。如果有,我希望它从向量中提取一些值。如果还没有,我希望它等到工作线程填充到所需的大小。
因为工作线程仍然可以向向量添加项目(可能导致 resize/move)我想我只能在工作线程挂起时执行此操作但是 TThread.Suspend( ) 已弃用。我花了几天时间查看 TMutex、TSemaphore 等,但文档很糟糕。谁能指出我正确的方向?
一个可能的解决方案是在工作线程中填充一个单独的较小向量,然后使用同步将其附加到大向量(等等),但我想避免这种情况。
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.
即使它没有被弃用,使用它仍然很危险。只有调试器应该挂起线程,这就是 SuspendThread()
API 的目的。
I've spent days looking at TMutex, TSemaphore etc. but the documentation is dire. Could anyone point me in the right direction?
您可以简单地用 TCriticalSection
or TMutex
包装对 vector 的所有访问,然后主线程和工作线程都可以在需要对 vector 执行任何操作时进入锁。例如:
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;
如果您发现工作线程比主线程更多地访问向量,您可以考虑使用 TMultiReadExclusiveWriteSynchronizer
or a SRW 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;
下面是我的努力。它避免了 Remy 的 TEvent 的复杂性和 J.. 的最后评论指出的 TCriticalSection 的陷阱。那是假设它有效。看起来确实如此,但如果有人能帮我找出我可能落入的陷阱,我将不胜感激。
用户将看到一个 TForm,其中包含一个名为 VecNdx 的 TEdit,用户可以使用它来输入他想要的向量值的索引和一个名为 GetVecVal 的 TButton,当单击它时,通过打印 VecNdx 的向量值来响应一个名为 VecVal 的 TLabel。
虽然向量值本身是由 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);
}
}
//---------------------------------------------------------------------------
假设我有一个工作线程填充在主线程中声明的一个大向量。虽然工作线程仍然是 运行(响应用户交互),但我希望主线程检查向量是否已填充到一定大小。如果有,我希望它从向量中提取一些值。如果还没有,我希望它等到工作线程填充到所需的大小。
因为工作线程仍然可以向向量添加项目(可能导致 resize/move)我想我只能在工作线程挂起时执行此操作但是 TThread.Suspend( ) 已弃用。我花了几天时间查看 TMutex、TSemaphore 等,但文档很糟糕。谁能指出我正确的方向?
一个可能的解决方案是在工作线程中填充一个单独的较小向量,然后使用同步将其附加到大向量(等等),但我想避免这种情况。
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.
即使它没有被弃用,使用它仍然很危险。只有调试器应该挂起线程,这就是 SuspendThread()
API 的目的。
I've spent days looking at TMutex, TSemaphore etc. but the documentation is dire. Could anyone point me in the right direction?
您可以简单地用 TCriticalSection
or TMutex
包装对 vector 的所有访问,然后主线程和工作线程都可以在需要对 vector 执行任何操作时进入锁。例如:
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;
如果您发现工作线程比主线程更多地访问向量,您可以考虑使用 TMultiReadExclusiveWriteSynchronizer
or a SRW 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;
下面是我的努力。它避免了 Remy 的 TEvent 的复杂性和 J.. 的最后评论指出的 TCriticalSection 的陷阱。那是假设它有效。看起来确实如此,但如果有人能帮我找出我可能落入的陷阱,我将不胜感激。
用户将看到一个 TForm,其中包含一个名为 VecNdx 的 TEdit,用户可以使用它来输入他想要的向量值的索引和一个名为 GetVecVal 的 TButton,当单击它时,通过打印 VecNdx 的向量值来响应一个名为 VecVal 的 TLabel。
虽然向量值本身是由 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);
}
}
//---------------------------------------------------------------------------