gpt4 book ai didi

multithreading - 我如何在执行线程内使用Dictionary/StringList-Delphi

转载 作者:行者123 更新时间:2023-12-03 19:40:20 32 4
gpt4 key购买 nike

我有一个线程类TValidateInvoiceThread:

type
TValidateInvoiceThread = class(TThread)
private
FData: TValidationData;
FInvoice: TInvoice; // Do NOT free
FPreProcessing: Boolean;

procedure ValidateInvoice;
protected
procedure Execute; override;
public
constructor Create(const objData: TValidationData; const bPreProcessing: Boolean);

destructor Destroy; override;
end;

constructor TValidateInvoiceThread.Create(const objData: TValidationData;
const bPreProcessing: Boolean);
var
objValidatorCache: TValidationCache;
begin
inherited Create(False);
FData := objData;
objValidatorCache := FData.Caches.Items['TInvAccountValidator'];
end;

destructor TValidateInvoiceThread.Destroy;
begin
FreeAndNil(FData);
inherited;
end;

procedure TValidateInvoiceThread.Execute;
begin
inherited;
ValidateInvoice;
end;

procedure TValidateInvoiceThread.ValidateInvoice;
var
objValidatorCache: TValidationCache;
begin
objValidatorCache := FData.Caches.Items['TInvAccountValidator'];
end;

我在另一个类中创建此线程
    procedure TInvValidators.ValidateInvoiceUsingThread(
const nThreadIndex: Integer;
const objValidatorCaches: TObjectDictionary<String, TValidationCache>;
const nInvoiceIndex: Integer; const bUseThread, bPreProcessing: Boolean);
begin
objValidationData := TValidationData.Create(FConnection, FAllInvoices, FAllInvoices[nInvoiceIndex], bUseThread);

objValidationData.Caches := objValidatorCaches;
objThread := TValidateInvoiceThread.Create(objValidationData, bPreProcessing);
FThreadArray[nThreadIndex] := objThread;
FHandleArray[nThreadIndex]:= FThreadArray[nThreadIndex].Handle;

end;

然后我执行它
  rWait:= WaitForMultipleObjects(FThreadsRunning, @FHandleArray, True, 100);

请注意,我已经从此处删除了一些代码,以尝试使其更简单易懂

问题是我的字典已损坏

如果我在构造函数中放置一个断点,一切都很好

但是,在Execute方法的第一行中,字典现在已损坏。

字典本身是该类的全局变量

我是否需要做一些特殊的事情以允许我在线程内使用字典?

我对字符串列表也遇到了同样的问题

编辑-根据要求提供其他信息

TInvValidators包含我的字典
  TInvValidators = class(TSTCListBase)
private
FThreadArray : Array[1..nMaxThreads] of TValidateInvoiceThread;
FHandleArray : Array[1..nMaxThreads] of THandle;
FThreadsRunning: Integer; // total number of supposedly running threads

FValidationList: TObjectDictionary<String, TObject>;
end;

procedure TInvValidators.Validate(
const Phase: TValidationPhase;
const objInvoices: TInvoices;
const ReValidate: TRevalidateInvoices;
const IDs: TList<Integer>;
const objConnection: TSTCConnection;
const ValidatorCount: Integer);
var
InvoiceIndex: Integer;
i : Integer;
rWait : Cardinal;
Flags: DWORD; // dummy variable used in a call to find out if a thread handle is valid
nThreadIndex: Integer;

procedure ValidateInvoiceRange(const nStartInvoiceID, nEndInvoiceID: Integer);
var
InvoiceIndex: Integer;
I: Integer;
begin
nThreadIndex := 1;

for InvoiceIndex := nStartInvoiceID - 1 to nEndInvoiceID - 1 do
begin
if InvoiceIndex >= objInvoices.Count then
Break;

objInvoice := objInvoices[InvoiceIndex];
ValidateInvoiceUsingThread(nThreadIndex, FValidatorCaches, InvoiceIndex, bUseThread, False);
Inc(nThreadIndex);

if nThreadIndex > nMaxThreads then
Break;
end;

FThreadsRunning := nMaxThreads;

repeat
rWait:= WaitForMultipleObjects(FThreadsRunning, @FHandleArray, True, 100);

case rWait of

// one of the threads satisfied the wait, remove its handle
WAIT_OBJECT_0..WAIT_OBJECT_0 + nMaxThreads - 1: RemoveHandle(rWait + 1);

// at least one handle has become invalid outside the wait call,
// or more than one thread finished during the previous wait,
// find and remove them
WAIT_FAILED:
begin
if GetLastError = ERROR_INVALID_HANDLE then
begin
for i := FThreadsRunning downto 1 do
if not GetHandleInformation(FHandleArray[i], Flags) then // is handle valid?
RemoveHandle(i);
end
else
// the wait failed because of something other than an invalid handle
RaiseLastOSError;
end;

// all remaining threads continue running, process messages and loop.
// don't process messages if the wait returned WAIT_FAILED since we didn't wait at all
// likewise WAIT_OBJECT_... may return soon
WAIT_TIMEOUT: Application.ProcessMessages;
end;

until FThreadsRunning = 0; // no more valid thread handles, we're done
end;

begin
try
FValidatorCaches := TObjectDictionary<String, TValidationCache>.Create([doOwnsValues]);

for nValidatorIndex := 0 to Count - 1 do
begin
objValidator := Items[nValidatorIndex];
objCache := TValidationCache.Create(objInvoices);
FValidatorCaches.Add(objValidator.ClassName, objCache);
objValidator.PrepareCache(objCache, FConnection, objInvoices[0].UtilityType);
end;

nStart := 1;
nEnd := nMaxThreads;
while nStart <= objInvoices.Count do
begin
ValidateInvoiceRange(nStart, nEnd);

Inc(nStart, nMaxThreads);
Inc(nEnd, nMaxThreads);
end;

finally
FreeAndNil(FMeterDetailCache);
end;
end;

如果我删除重复直到直到只剩下WaitForMultipleObjects,我仍然会收到很多错误

您可以在此处看到我正在不超过nMaxThreads(10)的大块中处理发票

当我恢复重复直到循环时,它可以在我的VM上运行,但随后在我的主机(具有更多可用内存)上的访问受到侵犯

保罗

最佳答案

在提供有关如何解决您的问题的指导之前,我将给您一个非常重要的提示。

First ensure your code works single-threaded, before trying to get a multi-threaded implementation working. The point is that multi-threaded code adds a whole new layer of complexity. Until your code works correctly in a single thread, it has no chance of doing so in multiple threads. And the extra layer of complexity makes it extremely difficult to fix.



您可能会相信您有一个有效的单线程解决方案,但是我发现您的代码中存在错误,这意味着您仍然有很多资源管理错误。这是一个仅包含相关行的示例,并附有注释以解释错误:
begin
try //try/finally is used for resource protection, in order to protect a
//resource correctly, it should be allocated **before** the try.
FValidatorCaches := TObjectDictionary<String, TValidationCache>.Create([doOwnsValues]);

finally
//However, in the finally you're destroying something completely
//different. In fact, there are no other references to FMeterDetailCache
//anywhere else in the code you've shown. This strongly implies an
//error in your resource protection.
FreeAndNil(FMeterDetailCache);
end;
end;

无法使用字典的原因

您说:“在Execute方法的第一行中,字典现在已损坏”。

首先,我相当确定您的字典并不是真的“损坏”。 “腐败”一词暗示着它存在,但是其内部数据无效,从而导致行为不一致。当 Execute方法想要使用字典时,它很有可能已经被破坏了。因此,您的线程基本上指向的是曾经有字典的内存区域,但现在根本不再存在。 (即不是“腐败”)

SIDE NOTE It is possible for your dictionary to truly become corrupt because you have multiple threads sharing the same dictionary. If different threads cause any internal changes to the dictionary at the same time, it could very easily become corrupt. But, assuming your threads are all treating the dictionary as read-only, you would need a memory overwrite to corrupt it.



因此,让我们集中讨论可能导致您的字典在线程使用它之前被破坏的原因。 注意我在提供的代码中看不到任何内容,但是有两种可能:
  • 您的主线程会在子线程开始使用字典之前将其销毁。
  • 您的一个子线程会在销毁字典后立即销毁该字典,从而导致所有其他线程无法使用它。

  • 在第一种情况下,将发生以下情况:
    Main Thread: ......C......D........
    Child Thread ---------S......

    . = code being executed
    C = child thread created
    - = child thread exists, but isn't doing anything yet
    S = OS has started the child thread
    D = main thread destroys dictionary

    上面的要点是,很容易忘记,即使子线程开始运行之前,主线程也可以达到决定销毁字典的程度。

    至于第二种可能性,这取决于 TValidationData的析构函数内部发生的事情。由于您没有显示该代码,因此只有您知道答案。

    调试以查明问题

    假设字典被销毁太早,只需进行一些调试就可以快速查明销毁字典的位置/原因。从您的问题来看,您似乎已经进行了一些调试,因此我假设您可以按照以下步骤操作:
  • 在字典的析构函数的第一行上放置一个断点。
  • 运行您的代码。
  • 如果在到达字典的析构函数之前已到达Execute,则该线程仍应能够使用字典。
  • 如果在到达Execute之前先到达字典的析构函数,则只需要检查导致对象破坏的调用顺序即可。

  • 内存覆盖时进行调试

    对内存覆盖的可能性保持开放态度...调试起来有点棘手。但是,只要您能够始终如一地重现该问题,就应该可以进行调试。
  • 在线程的析构函数中放置一个断点。
  • 运行应用程序
  • 当您到达上述断点时,请按Ctrl + F7并评估@FData.Caches来找到字典的地址。
  • 现在,为字典的地址和大小添加一个数据断点(使用Breakpoints窗口中的下拉菜单)。
  • 继续运行,当数据更改时,应用程序将暂停。
  • 再次,检查调用堆栈以确定原因。

  • 包起来

    您有许多问题和陈述暗示对线程之间共享数据(字典/字符串列表)存在误解。我会在这里尝试掩盖这些内容。
  • 在线程中使用Dictionary/StringList不需要任何特殊要求。它与将其传递给任何其他对象基本上相同。只要确保Dictionary/StringList不会被过早销毁即可。
  • 也就是说,每当您共享数据时,都需要意识到“竞赛条件”的可能性。 IE。一个线程尝试同时访问共享数据,而另一个线程正忙于对其进行修改。如果没有线程在修改数据,则无需担心。但是,只要任何线程能够修改数据,就需要使访问成为“线程安全的”。 (有多种方法可以执行此操作,请搜索关于SO的现有问题。)
  • 您提到:“字典本身是该类的全局变量”。您的用语不正确。全局变量是在单元级别声明的,可以在任何地方访问。仅仅说字典是该类的成员或字段就足够了。与“全局变量”打交道时,有很多事情需要担心。因此最好避免任何困惑。
  • 您可能需要重新考虑如何初始化线程。有一些原因会导致您无法初始化FHandleArray的某些条目。你可以吗?
  • 您提到一台具有更多可用内存的计算机上的AV。注意:内存量无关紧要。而且,如果您以32位模式运行,则在任何情况下您都无法访问超过4 GB的空间。

  • 最后,要特别提及:

    Using multiple threads to perform dictionary lookups is extremely inefficient. A dictionary lookup is an O(1) operation. The overhead of threading will almost certainly slow you down unless you intend doing a significant amount of processing in addition to the dictionary lookup.



    PS-(不太大)错误

    我在您的代码中注意到以下内容,这是一个错误。
    procedure TValidateInvoiceThread.Execute;
    begin
    inherited;
    ValidateInvoice;
    end;
    TThread.Execute方法是 抽象,这意味着没有实现。尝试调用抽象方法将触发 EAbstractError。幸运的是,正如LU RD所指出的那样,编译器可以通过不对行进行编译来保护您。即使这样,在此处不调用 继承自会更准确。

    注意:通常,被覆盖的方法并不总是需要调用 继承自。您应该明确知道 继承的在为您做什么,并决定是否逐案调用它。不要仅仅因为您要覆盖虚拟方法而进入调用 继承了的自动驾驶模式。

    关于multithreading - 我如何在执行线程内使用Dictionary/StringList-Delphi,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/29480402/

    32 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com