从主线程更改线程的变量值是否安全?
It is safe to change variable values of a thread from the main thread?
我写了一个简单的组件来监视文件夹并在检测到更改时触发事件。它运作良好......显然。但我不确定一件事。有时,主线程可能需要更新被监控的路径,我不确定我是否做对了。它是关于 SetNewPath
程序的。这是从主线程执行的,它从另一个线程更改 UpdatePath
变量。当主线程写入 UpdatePath
而组件线程试图在 Execute
循环中读取其值时,可能会产生冲突 ?
FolderMonitor.pas
unit FolderMonitor;
interface
uses
SysUtils, Windows, Classes, ExtCtrls;
type
TOnFolderChange = procedure(Sender: TObject) of object;
TFolderMonitor = class(TThread)
private
MainWait: THandle;
UpdatePath: Boolean;
TimeOut: Cardinal;
FPath: String;
FOnFolderChange: TOnFolderChange;
procedure DoOnFolderChange;
procedure SetNewPath(Path:String);
protected
procedure Execute; override;
public
constructor Create(const FolderPath: String; OnFolderChangeHandler: TOnFolderChange);
destructor Destroy; override;
procedure Unblock;
property Path: String read FPath write SetNewPath;
property OnFolderChange: TOnFolderChange read FOnFolderChange write FOnFolderChange;
end;
implementation
constructor TFolderMonitor.Create(const FolderPath: String; OnFolderChangeHandler: TOnFolderChange);
begin
inherited Create(True);
FOnFolderChange:=OnFolderChangeHandler;
FPath:=FolderPath;
UpdatePath:=false;
FreeOnTerminate:=false;
MainWait:=CreateEvent(nil,true,false,nil);
Resume;
end;
destructor TFolderMonitor.Destroy;
begin
CloseHandle(MainWait);
inherited;
end;
procedure TFolderMonitor.DoOnFolderChange;
begin
if Assigned(FOnFolderChange) then
Synchronize(procedure
begin
FOnFolderChange(Self);
end);
end;
procedure TFolderMonitor.Unblock;
begin
PulseEvent(MainWait);
end;
procedure TFolderMonitor.SetNewPath(Path:String);
begin
FPath:=Path;
UpdatePath:=true;
PulseEvent(MainWait);
end;
procedure TFolderMonitor.Execute;
var Filter,WaitResult: Cardinal;
WaitHandles: array[0..1] of THandle;
begin
Filter:=FILE_NOTIFY_CHANGE_DIR_NAME + FILE_NOTIFY_CHANGE_FILE_NAME + FILE_NOTIFY_CHANGE_SIZE;
WaitHandles[0]:=MainWait;
WaitHandles[1]:=FindFirstChangeNotification(PWideChar(FPath),false,Filter);
TimeOut:=INFINITE;
while not Terminated do begin
if UpdatePath then begin
if WaitHandles[1]<>INVALID_HANDLE_VALUE then FindCloseChangeNotification(WaitHandles[1]);
WaitHandles[1]:=FindFirstChangeNotification(PWideChar(FPath),false,Filter);
TimeOut:=INFINITE;
UpdatePath:=false;
end;
if WaitHandles[1] = INVALID_HANDLE_VALUE
then WaitResult:=WaitForSingleObject(WaitHandles[0],INFINITE)
else WaitResult:=WaitForMultipleObjects(2,@WaitHandles,false,TimeOut);
case WaitResult of
WAIT_OBJECT_0: Continue;
WAIT_OBJECT_0+1: TimeOut:=200;
WAIT_TIMEOUT: begin DoOnFolderChange; TimeOut:=INFINITE; end;
end;
if WaitHandles[1] <> INVALID_HANDLE_VALUE then
FindNextChangeNotification(WaitHandles[1]);
end;
if WaitHandles[1] <> INVALID_HANDLE_VALUE then
FindCloseChangeNotification(WaitHandles[1]);
end;
end.
UnitMain.pas
unit UnitMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, FolderMonitor;
type
TForm1 = class(TForm)
Memo1: TMemo;
Edit1: TEdit;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure OnFolderChange(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
end;
var
Form1: TForm1;
Mon: TFolderMonitor;
X: integer;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
X:=0;
Mon:=TFolderMonitor.Create('D:\Test',OnFolderChange);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Mon.Terminate;
Mon.Unblock;
Mon.WaitFor;
Mon.Free;
end;
procedure TForm1.OnFolderChange(Sender: TObject);
begin
inc(x);
Memo1.Lines.Add('changed! '+IntToStr(x));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Mon.Path:=Edit1.Text;
end;
end.
您有一个在多个线程之间共享的变量,一个线程修改该变量。这种情况称为数据竞争。
有些种族可能是良性的。这个不是。如果一个线程修改变量而另一个线程读取它,则可能会发生错误。因为数据类型很复杂(指向堆分配的字符数组的指针),读取线程很可能会尝试从释放的内存中读取。
对于像这样的复杂类型,无论何时访问该值都需要使用互斥锁。所有的读取和写入都必须由锁序列化。使用临界区或监视器。
为确保您永远不会执行不受保护的访问,明智的做法是在代码中强制执行此规则。例如,我的 TThreadSafe<T>
在这里描述:Generic Threadsafe Property
我写了一个简单的组件来监视文件夹并在检测到更改时触发事件。它运作良好......显然。但我不确定一件事。有时,主线程可能需要更新被监控的路径,我不确定我是否做对了。它是关于 SetNewPath
程序的。这是从主线程执行的,它从另一个线程更改 UpdatePath
变量。当主线程写入 UpdatePath
而组件线程试图在 Execute
循环中读取其值时,可能会产生冲突 ?
FolderMonitor.pas
unit FolderMonitor;
interface
uses
SysUtils, Windows, Classes, ExtCtrls;
type
TOnFolderChange = procedure(Sender: TObject) of object;
TFolderMonitor = class(TThread)
private
MainWait: THandle;
UpdatePath: Boolean;
TimeOut: Cardinal;
FPath: String;
FOnFolderChange: TOnFolderChange;
procedure DoOnFolderChange;
procedure SetNewPath(Path:String);
protected
procedure Execute; override;
public
constructor Create(const FolderPath: String; OnFolderChangeHandler: TOnFolderChange);
destructor Destroy; override;
procedure Unblock;
property Path: String read FPath write SetNewPath;
property OnFolderChange: TOnFolderChange read FOnFolderChange write FOnFolderChange;
end;
implementation
constructor TFolderMonitor.Create(const FolderPath: String; OnFolderChangeHandler: TOnFolderChange);
begin
inherited Create(True);
FOnFolderChange:=OnFolderChangeHandler;
FPath:=FolderPath;
UpdatePath:=false;
FreeOnTerminate:=false;
MainWait:=CreateEvent(nil,true,false,nil);
Resume;
end;
destructor TFolderMonitor.Destroy;
begin
CloseHandle(MainWait);
inherited;
end;
procedure TFolderMonitor.DoOnFolderChange;
begin
if Assigned(FOnFolderChange) then
Synchronize(procedure
begin
FOnFolderChange(Self);
end);
end;
procedure TFolderMonitor.Unblock;
begin
PulseEvent(MainWait);
end;
procedure TFolderMonitor.SetNewPath(Path:String);
begin
FPath:=Path;
UpdatePath:=true;
PulseEvent(MainWait);
end;
procedure TFolderMonitor.Execute;
var Filter,WaitResult: Cardinal;
WaitHandles: array[0..1] of THandle;
begin
Filter:=FILE_NOTIFY_CHANGE_DIR_NAME + FILE_NOTIFY_CHANGE_FILE_NAME + FILE_NOTIFY_CHANGE_SIZE;
WaitHandles[0]:=MainWait;
WaitHandles[1]:=FindFirstChangeNotification(PWideChar(FPath),false,Filter);
TimeOut:=INFINITE;
while not Terminated do begin
if UpdatePath then begin
if WaitHandles[1]<>INVALID_HANDLE_VALUE then FindCloseChangeNotification(WaitHandles[1]);
WaitHandles[1]:=FindFirstChangeNotification(PWideChar(FPath),false,Filter);
TimeOut:=INFINITE;
UpdatePath:=false;
end;
if WaitHandles[1] = INVALID_HANDLE_VALUE
then WaitResult:=WaitForSingleObject(WaitHandles[0],INFINITE)
else WaitResult:=WaitForMultipleObjects(2,@WaitHandles,false,TimeOut);
case WaitResult of
WAIT_OBJECT_0: Continue;
WAIT_OBJECT_0+1: TimeOut:=200;
WAIT_TIMEOUT: begin DoOnFolderChange; TimeOut:=INFINITE; end;
end;
if WaitHandles[1] <> INVALID_HANDLE_VALUE then
FindNextChangeNotification(WaitHandles[1]);
end;
if WaitHandles[1] <> INVALID_HANDLE_VALUE then
FindCloseChangeNotification(WaitHandles[1]);
end;
end.
UnitMain.pas
unit UnitMain;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, FolderMonitor;
type
TForm1 = class(TForm)
Memo1: TMemo;
Edit1: TEdit;
Button1: TButton;
procedure FormCreate(Sender: TObject);
procedure OnFolderChange(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure Button1Click(Sender: TObject);
end;
var
Form1: TForm1;
Mon: TFolderMonitor;
X: integer;
implementation
{$R *.dfm}
procedure TForm1.FormCreate(Sender: TObject);
begin
X:=0;
Mon:=TFolderMonitor.Create('D:\Test',OnFolderChange);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Mon.Terminate;
Mon.Unblock;
Mon.WaitFor;
Mon.Free;
end;
procedure TForm1.OnFolderChange(Sender: TObject);
begin
inc(x);
Memo1.Lines.Add('changed! '+IntToStr(x));
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
Mon.Path:=Edit1.Text;
end;
end.
您有一个在多个线程之间共享的变量,一个线程修改该变量。这种情况称为数据竞争。
有些种族可能是良性的。这个不是。如果一个线程修改变量而另一个线程读取它,则可能会发生错误。因为数据类型很复杂(指向堆分配的字符数组的指针),读取线程很可能会尝试从释放的内存中读取。
对于像这样的复杂类型,无论何时访问该值都需要使用互斥锁。所有的读取和写入都必须由锁序列化。使用临界区或监视器。
为确保您永远不会执行不受保护的访问,明智的做法是在代码中强制执行此规则。例如,我的 TThreadSafe<T>
在这里描述:Generic Threadsafe Property