草庐IT

关于多线程:主线程阻塞并行线程?

codeneng 2023-03-28 原文

Main thread blocking parallel thread?

创建一个 VCL Forms 应用程序,在 Form 上放置一个 TButton 和一个 TMemo,并在按钮的 OnClick 处理程序中编写此代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
uses
  OtlParallel, OtlTaskControl;

procedure TForm2.btnStartLoopClick(Sender: TObject);
var
  starttime: Cardinal;
  k: Integer;
begin
  mmoTest.Lines.Clear;
  for k := 1 to 50 do
    mmoTest.Lines.Add('Line ' + IntToStr(k));

  starttime := GetTickCount;
  Parallel.Async(
    procedure
    var
      i: Integer;
    begin
      for i := 1 to 50 do
      begin
        Sleep(100);
        mmoTest.Lines[i - 1] := mmoTest.Lines[i - 1] + FormatDateTime(' nn:ss:zzz', Now);
      end;
    end,
    Parallel.TaskConfig.SetPriority(TOTLThreadPriority.tpHighest).OnTerminated(
    procedure
    begin
      mmoTest.Lines.Add(IntToStr(GetTickCount - starttime) + ' milliseconds');
    end));
end;

现在运行程序并进行以下测试:

  • 点击按钮,等待循环完成并查看备忘录最后一行显示的时间:应该是大约5300毫秒。

  • 现在再次单击按钮,单击并按住表单的标题栏并快速移动表单直到循环结束。现在再看一下备忘录的最后一行:在我的测试中,时间超过了 7000 毫秒。很明显,主线程阻塞了并行线程!

  • 那么如何避免主线程阻塞并行线程呢?

    • -"显然,..." - 为什么?首先,我建议您打开任何 cpu 使用监控程序,然后按住任何窗口的标题栏并快速移动,同时观察它对 cpu 使用的影响。如果它有什么重要意义,那么你的假设是没有实际意义的。
    • Remy,你能举一个小代码示例吗?


    首先,此代码不是线程安全的,因为异步代码直接从主 UI 线程之外的任务线程访问 TMemo。你不能这样做。工作线程必须与 UI 线程同步才能安全地访问 UI 控件,否则可能会发生坏事。您可以使用 TThread.Synchronize()TThread.Queue()IOmniTask.Invoke() 进行同步。

    其次,当您在标题栏上按住鼠标时,主 UI 消息循环被阻止(操作系统正在运行一个单独的模式消息循环,直到您松开鼠标)。因此,在主消息循环重新获得控制权之前,可能不会运行任务的 OnTerminate 事件处理程序。这将解释为什么您的计时器持续时间据报道比预期的要长,而不是因为任务循环被阻塞。

    第三,Sleep()不是绝对的。它将至少hibernate请求的时间,但可能会hibernate更长时间。因此,您的任务循环将运行至少 5 秒,但可能会更长一点。

    • 关于"从主 UI 线程之外的任务线程访问 TMemo":我假设此规则仅对修改 UI 控件的工作线程有效,而不适用于从 UI 控件读取值?
    • @user1580348:不。任何访问,读取或写入,都必须同步。主要原因是 TWinControl.Handle 属性不是线程安全的,它可能会在单独的线程使用 HWND 时更改值,这会导致各种问题。因此,即使读取与 HWND 相关的数据也必须受到保护。
    • 您还可以通过 PostMessage() 和自定义消息发送将在备忘录中显示的字符串。
    • @Nat 仅当您将其发布到保证不会在任务线程运行时动态重新分配的 HWND 时,例如 TApplication.Handle,或者您直接使用 AllocateHWnd()CreateWindow/Ex() 分配的 HWND .然后消息处理程序可以将接收到的字符串添加到备忘录中。
    • 但是,如果程序逻辑和程序流程肯定排除了另一个线程与工作线程同时访问 UI 控件怎么办?在这种情况下,不应该允许从工作线程访问 UI 控件吗?
    • @user1580348 不,因为主 UI 线程本身仍然会随机接触控件,例如响应操作系统消息。并且控件的 Handle 属性值可以随时动态重新创建。如果一个工作线程在重新创建开始之后和新的 HWND 准备好之前接触到控件,那么会发生非常糟糕的事情。这是一种具有恶劣后果的竞争条件。所以最好避免它。
    • @RemyLebeau 够公平的。如果只是 UI 更新,那或许你不需要太担心。只需确保任何新句柄在更改时都传递给线程。否则,我经常给我的线程一个从 AllocateHWnd()PostMessage() 的窗口句柄,然后我将一个事件调用回主窗体。

    有关关于多线程:主线程阻塞并行线程?的更多相关文章

    1. ruby - RuntimeError(自动加载常量 Apps 多线程时检测到循环依赖 - 2

      我收到这个错误:RuntimeError(自动加载常量Apps时检测到循环依赖当我使用多线程时。下面是我的代码。为什么会这样?我尝试多线程的原因是因为我正在编写一个HTML抓取应用程序。对Nokogiri::HTML(open())的调用是一个同步阻塞调用,需要1秒才能返回,我有100,000多个页面要访问,所以我试图运行多个线程来解决这个问题。有更好的方法吗?classToolsController0)app.website=array.join(',')putsapp.websiteelseapp.website="NONE"endapp.saveapps=Apps.order("

    2. ruby - 如何让Ruby捕获线程中的语法错误 - 2

      我正在尝试使用ruby​​编写一个双线程客户端,一个线程从套接字读取数据并将其打印出来,另一个线程读取本地数据并将其发送到远程服务器。我发现的问题是Ruby似乎无法捕获线程内的错误,这是一个示例:#!/usr/bin/rubyThread.new{loop{$stdout.puts"hi"abc.putsefsleep1}}loop{sleep1}显然,如果我在线程外键入abc.putsef,代码将永远不会运行,因为Ruby将报告“undefinedvariableabc”。但是,如果它在一个线程内,则没有错误报告。我的问题是,如何让Ruby捕获这样的错误?或者至少,报告线程中的错误?

    3. ruby - 如何在 ruby​​ 中运行后台线程? - 2

      我是ruby​​的新手,我认为重新构建一个我用C#编写的简单聊天程序是个好主意。我正在使用Ruby2.0.0MRI(Matz的Ruby实现)。问题是我想在服务器运行时为简单的服务器命令提供I/O。这是从示例中获取的服务器。我添加了使用gets()获取输入的命令方法。我希望此方法在后台作为线程运行,但该线程正在阻塞另一个线程。require'socket'#Getsocketsfromstdlibserver=TCPServer.open(2000)#Sockettolistenonport2000defcommandsx=1whilex==1exitProgram=gets.chomp

    4. ruby - 带括号和 splat 运算符的并行赋值 - 2

      我明白了:x,(y,z)=1,*[2,3]x#=>1y#=>2z#=>nil我想知道为什么z的值为nil。 最佳答案 x,(y,z)=1,*[2,3]右侧的splat*是内联扩展的,所以它等同于:x,(y,z)=1,2,3左边带括号的列表被视为嵌套赋值,所以它等价于:x=1y,z=23被丢弃,而z被分配给nil。 关于ruby-带括号和splat运算符的并行赋值,我们在StackOverflow上找到一个类似的问题: https://stackoverflow

    5. ruby - 使对象的行为类似于 ruby​​ 中并行分配的数组 - 2

      假设您在Ruby中执行此操作:ar=[1,2]x,y=ar然后,x==1和y==2。是否有一种方法可以在我自己的类中定义,从而产生相同的效果?例如rb=AllYourCode.newx,y=rb到目前为止,对于这样的赋值,我所能做的就是使x==rb和y=nil。Python有这样一个特性:>>>classFoo:...def__iter__(self):...returniter([1,2])...>>>x,y=Foo()>>>x1>>>y2 最佳答案 是的。定义#to_ary。这将使您的对象被视为要分配的数组。irb>o=Obje

    6. ruby - Rails 开发服务器、PDFKit 和多线程 - 2

      我有一个使用PDFKit呈现网页的pdf版本的Rails应用程序。我使用Thin作为开发服务器。问题是当我处于开发模式时。当我使用“bundleexecrailss”启动我的服务器并尝试呈现任何PDF时,整个过程会陷入僵局,因为当您呈现PDF时,会向服务器请求一些额外的资源,如图像和css,看起来只有一个线程.如何配置Rails开发服务器以运行多个工作线程?非常感谢。 最佳答案 我找到的最简单的解决方案是unicorn.geminstallunicorn创建一个unicorn.conf:worker_processes3然后使用它:

    7. ruby-on-rails - 关于 Ruby 的一般问题 - 2

      我在我的rails应用程序中安装了来自github.com的acts_as_versioned插件,但有一段代码我不完全理解,我希望有人能帮我解决这个问题class_eval我知道block内的方法(或任何它是什么)被定义为类内的实例方法,但我在插件的任何地方都找不到定义为常量的CLASS_METHODS,而且我也不确定是什么here,并且有问题的代码从lib/acts_as_versioned.rb的第199行开始。如果有人愿意告诉我这里的内幕,我将不胜感激。谢谢-C 最佳答案 这是一个异端。http://en.wikipedia

    8. ruby - Ruby 1.9.1 中的 native 线程,对我有什么好处? - 2

      所以,Ruby1.9.1现在是declaredstable.Rails应该与它一起工作,并且正在慢慢地将gem移植到它。它具有native线程和全局解释器锁(GIL)。自从GIL到位后,原生线程是否比1.9.1中的绿色线程有任何优势? 最佳答案 1.9中的线程是原生的,但它们被“放慢了速度”,一次只允许一个线程运行。这是因为如果线程真的并行运行,它会混淆现有代码。优点:IO现在在线程中是异步的。如果一个线程阻塞在IO上,那么另一个线程将继续执行直到IO完成。C扩展可以使用真正的线程。缺点:任何非线程安全的C扩展都可能存在使用Thre

    9. ruby - 使写入文件线程安全 - 2

      我在一个ruby​​文件中有一个函数可以像这样写入一个文件File.open("myfile",'a'){|f|f.puts("#{sometext}")}这个函数在不同的线程中被调用,使得像上面这样的文件写入不是线程安全的。有谁知道如何以最简单的方式使这个文件写入线程安全?更多信息:如果重要的话,我正在使用rspec框架。 最佳答案 您可以通过File#flock给锁File.open("myfile",'a'){|f|f.flock(File::LOCK_EX)f.puts("#{sometext}")}

    10. ruby - 我怎样才能更好地了解/了解更多关于 Ruby 的知识? - 2

      按照目前的情况,这个问题不适合我们的问答形式。我们希望答案得到事实、引用或专业知识的支持,但这个问题可能会引发辩论、争论、投票或扩展讨论。如果您觉得这个问题可以改进并可能重新打开,visitthehelpcenter指导。关闭9年前。我最近开始学习Ruby,这是我的第一门编程语言。我对语法感到满意,并且我已经完成了许多只教授相同基础知识的教程。我已经写了一些小程序(包括我自己的数组排序方法,在有人告诉我谷歌“冒泡排序”之前我认为它非常聪明),但我觉得我需要尝试更大更难的东西来理解更多关于Ruby.关于如何执行此操作的任何想法?

    随机推荐