如何锁定记录数组以在线程中添加和删除元素

Posted

技术标签:

【中文标题】如何锁定记录数组以在线程中添加和删除元素【英文标题】:How can lock array of records for adding and deleting element in thread 【发布时间】:2022-01-20 08:59:27 【问题描述】:

我想一次又一次地从服务器获取一些查询,并在记录数组中列出这个 URL 查询。

为了释放内存,必须释放已处理的数组的元素。 此进程在线程上,以防止 App 冻结。 如果数组未锁定,则删除元素可能会引发异常,因为其他元素正在处理中,或者添加或删除元素的索引已更改。

我的问题是:'?'

为了理解我的实际应用代码,简化了这个示例代码:

uses  IdHTTP;

type 
  tMyRecAra=record
    sta:integer;
    url:string;
    // ...
    // ...
  end;

var MyRecAra: array of tMyRecAra;

procedure TfoTestAra.btAddClick(Sender: TObject);
var cou:integer;
begin
  //+ start lock array MyRecAra ?
    cou:=length(MyRecAra);
    SetLength(MyRecAra, length(MyRecAra)+1);
    MyRecAra[cou].sta:=0;
    MyRecAra[cou].url:='http:/......';
  //- stop lock array MyRecAra ?    
end;

procedure TfoTestAra.btStartClick(Sender: TObject);
var
  IdHTTP1:TIdHTTP;
  mainThr,curThr : TThread;
  cStream:TMemoryStream;
begin
  mainThr := TThread.CreateAnonymousThread(
    procedure
      begin
        while true or other condition  do
          begin
            curThr := TThread.CreateAnonymousThread(
              procedure
              var i:integer;
              begin
                //+ start lock array MyRecAra ?
                  for i := 0 to (length(MyRecAra)-1) do
                    begin
                      if (MyRecAra[i].sta=0) then
                        begin
                          MyRecAra[i].sta:=1;
                          //...
                          //..
                           for example : IdHTTP1.Get(MyRecAra[i].url,cStream);
                          //...
                          //..
                        end;
                      end;
                //- stop lock array MyRecAra ?
              end);
            curThr.Start;
            sleep(5000);
          end;
      end);
  mainThr.start;
end;

procedure TfoTestAra.Timer1Timer(Sender: TObject);
var 
  sumFee:integer;
  i, j:integer;
begin
  // timer.interval=10000;

  //+ start lock array MyRecAra?
    sumFee:=0;
    for i := 0 to (length(MyRecAra)-1) do
    begin
      if (MyRecAra[i].sta=1) then
        begin
          inc(sumFee);
          for j := (i+1) to  sumFee-1 do
            begin
              if  (MyRecAra[j].sta <> 1) then
                MyRecAra[i]:=MyRecAra[j]
            end;

        end;
      end;
      if sumFee<>0 then
        SetLength(MyRecAra, (length(MyRecAra)-sumFee));
   //+ stop lock array MyRecAra ?
end;

End.

【问题讨论】:

@Dalija Prasnikar。您的解决方案对于需要锁定和解锁内存的其他人很有用,并且它在我的简单代码上完美运行,但我的主要代码非常复杂,应用此方法需要对其进行大量更改。请再次附上您的学习答案,供他人参考。 当你准备好之后,你应该再问一个问题。在这里看起来绝对不正确的一件事是对 Timer 进行清理。 【参考方案1】:

您可以使用锁来保护对共享数据的访问,然后在您访问数据的所有地方的一般模式是:

Lock.Enter;
try
  // procected code
finally
  Lock.Leave;
end;

您需要在与需要保护的数据相同的范围内声明锁变量,并且您需要在第一次使用该锁之前对其进行初始化,并在不再需要时将其释放。

例如,如果你的MyRecAra是单元中的全局数据,那么Lock也需要是全局的,在单元的初始化部分初始化,在终结部分释放。

如果MyRecAra 是表单或其他类中的字段,那么Lock 也将是该类中的字段,在构造函数中初始化并在析构函数中释放。

常用的锁是TCriticalSection。还有其他类型的锁,但首先这个就可以了。

var
  Lock: TCriticalSection;
  MyRecAra: TMyRecAra; 

initialization

  Lock := TCriticalSection.Create;

finalization

  Lock.Free;

end.

【讨论】:

谢谢。我将您的方法添加到我的示例代码中,它工作正常,但是当我将这种方式添加到主代码中时,我的应用程序挂起并且在跟踪它时,我看到'lock.enter'在线程内发生了很多次,然后' lock.leave' 一个接一个地发生,最后在 'lock.enter' 行之一上跟踪停止。提到我用这个结构封装了我的所有代码:'try....lock.enter...finally...lock.leave...end' Enter 必须在try 之前,如 Dalija 的回答所示。确保每个成功的Enter都有对应的Leave 除了 Remy 所说的之外,您还有使用锁的模板,但这并不意味着您会很轻松。您正在从后台线程和主线程请求锁定。如果您在后台线程中长时间持有锁,则主线程将被冻结,直到它设法获取锁,反之亦然。有多种方法可以解决此类问题,但解决方案很大程度上取决于您要完成的工作。 像一个简单的网络应用程序(例如 WhatsApp),我想从服务器上的数据库请求查询,http 协议的基础,在从服务器接收到结果后,继续分析接收到的数据并影响我的应用程序库那个结果。这个请求保存到一个数组中,然后一个一个地进行。您的解决方案非常有用,但是在我的应用程序上,有很多分散并应用您的建议(防止嵌套线程和使用正确的 try...finally...end ),我无法阻止我的应用程序挂起。 你需要改变你的完整代码逻辑。但是关于你在做什么有太多的未知数,无法为你提供如何处理它的准确答案。例如,您在计时器中做什么 - 您是否在刷新数据,如果是这样,那么这是错误的方法,您应该在计时器上而不是在实际准备好新数据时从后台线程发出刷新信号。从服务器检索结果时是否需要保持阵列锁定。更好的选择是在一些临时存储中检索结果,并且仅在添加新数据时锁定数组。

以上是关于如何锁定记录数组以在线程中添加和删除元素的主要内容,如果未能解决你的问题,请参考以下文章

调用condition_variable等待函数时线程如何等待?

node.js如何删除数组子文档的元素?

PHP学习记录数组中的数组的指针

如何删除一个list中最后一个元素

java中 如何在一个数组中删除里面的元素

java数组如何循环添加元素