Halcon之并行编程

471 阅读14分钟

并行编程和Halcon

解释如何在多核或多处理器硬件上使用 HALCON,重点介绍主要功能:自动并行化(第 1 节)和并行编程的支持(第 2 节)。

1、自动并行化

如果在多处理器或多核硬件上使用 HALCON,它将自动并行化图像处理操作符。 第 1.1 节描述了如何初始化 HALCON 以使用此机制。 1.2 节解释了 HALCON 操作员用于自动并行化的不同方法。

1.1 初始化 HALCON

为了使并行化机制最佳地适应实际硬件,HALCON 需要检查该硬件一次。 之后,HALCON 程序将自动并行化,无需您采取任何进一步操作。 即使现有的 HALCON 程序也可以运行并并行化,无需更改。 您可以通过调用运算符 optimize_aop 来触发此初始检查(有关详细信息,请参阅 HALCON 参考手册中的相应条目)。 请注意,此操作符只有在多核或多处理器硬件上调用时才能正常工作; 如果您在单处理器或单核计算机上调用操作员,它将返回错误消息。 作为一种快捷方式,您可以调用位于 %HALCONROOT%\bin%HALCONARCH% 目录中的可执行文件 hcheck_parallel。 在调用 optimize_aop 时,HALCON 会检查原则上可以通过自动并行化加速的每个运算符。 每个被检查的算子都被多次处理 - 顺序和并行 - 使用一组不断变化的输入参数值,例如图像。 后者有助于评估算子的输入参数特征(例如输入图像的大小)与其并行处理效率之间的依赖关系。 请注意,此检查可能需要几个小时,具体取决于您的计算机和优化参数! 提取的信息存储在通用应用程序数据文件夹(Windows 下)或HALCON 安装目录$HALCONROOT(Linux 下)中的.aop_info 文件中。 请注意,在某些操作系统上,您需要特殊权限才能成功初始化 HALCON,否则操作符 optimize_aop 无法存储提取的信息。 请注意,为了在 Windows Vista 及更高版本下以管理员权限执行命令行工具,您需要选择“以管理员身份运行”(即使您已经以管理员身份登录)。 请参阅目录 %HALCONEXAMPLES%\hdevelop\System\Parallelization 中的示例,了解有关 optimize_aop 和其他允许查询和修改并行化信息的运算符的更多信息。

1.2 自动并行化方法

对于算子的自动并行化,HALCON 利用了数据并行性,即算子的部分输入数据可以相互独立处理的特性。数据并行性可以在四个级别找到:

  1. 元组级别 如果使用包含元组(即图像、区域或 XLD 的数组)的图标输入参数调用运算符,则可以通过在并行线程上分布元组元素(即单个图像、区域或 XLD)来并行化。此方法要求所有输入参数包含相同数量的元组元素(或包含单个图标对象或值)。
  2. 渠道层面 如果使用包含多个通道的输入图像调用算子,则可以通过在并行线程上分配通道来并行化。此方法要求所有输入图像对象包含相同数量的通道或单个通道图像。
  3. 域级 支持该级别的算子可以通过划分其域并将其部分分布在并行线程上来并行化。
  4. 内部数据层面 只有部分操作符被并行化。实际的并行度取决于算子的实现。因此,多核系统的潜在加速因使用这种并行化方法的运营商而异。 参考手册中对 HALCON 运算符的描述包含一个名为“并行化”的条目,它指定了在多核或多处理器硬件上使用 HALCON 时的行为。此条目指示操作符是否会被 HALCON 自动并行化以及通过哪种方法(元组、通道、域、内部数据)。 也可以使用 get_operator_info 确定任意运算符 opname 的并行化方法:
get_operator_info('opname', 'parallel_method', Information)

2、 使用 HALCON 进行并行编程

HALCON 通过线程安全和可重入来支持并行编程,即不同的线程可以同时调用 HALCON 操作符而无需等待。 但是,并非所有运算符都是完全可重入的。 本节将仔细研究 HALCON 的可重入性。 此外,它还指出了在编写使用 HALCON 的并行程序时应牢记的问题。 目录example\c 中的示例程序example_multithreaded1.c 展示了如何使用多线程使用HALCON/C 并行提取板上不同类型的组件。 此外,HALCON 提供了特殊的操作符来同步线程

2.1 仔细看看可重入性

事实上,HALCON 操作符有不同“级别”的可重入性:

  1. 可重入 如果一个操作符可以被多个线程同时调用,而与调用它的数据无关,那么它就是完全可重入的。 请注意,当多个线程使用相同的数据对象(例如,相同的图像变量)时,您必须特别小心。在这种情况下,您必须使用相应的并行编程机制(互斥体、信号量)手动同步对该变量的访问。更好的是尽可能避免这种情况,即使用局部变量。请注意,这不是 HALCON 的特殊问题,而是一般的并行编程问题。
  2. 本地 此级别的可重入性仅在 Windows 下相关。 在 Windows 下,标记为 local 的运算符只能从实例化相应对象的线程中调用。典型的例子是使用图形 I/O 函数的操作符,应该只在主线程中使用。原因是在 Windows 下,程序线程和图形元素(例如窗口、对话框或按钮控件)之间存在直接映射。简而言之,图形元素仅存在于其关联线程的上下文中。如果一个线程试图通过属于另一个线程的图形元素执行用户交互,这可能会导致严重的问题(例如,挂起应用程序)。例如,如果一个线程通过 open_window 打开一个窗口,而另一个线程试图通过 draw_circle 从该窗口获取输入,您可能会遇到死锁。
  3. 单写多读 只有当不同的调用线程处理不同的数据时,才应同时调用特定的一组运算符。例如,线程不应该尝试通过使用相同的句柄调用 adapt_template 来同时修改相同的模板以进行模式匹配。这同样适用于一个线程不应该修改另一个线程同时读取的数据集的情况。具有此行为的其他运算符组与文件 I/O(例如,write_image – read_image、fwrite_string – fread_string 以及非 HALCON 文件命令)或背景估计(例如,update_bg_esti – give_bg_esti)有关。 由于一般不推荐这种线程行为,HALCON 不会主动阻止它,从而节省开销。这意味着如果您(不小心)同时使用相同的数据调用此类运算符,则不会有线程阻塞,但您可能会得到不受欢迎的效果。
  4. 互斥 有些操作符不能被多个线程同时调用,但可以与其他 HALCON 操作符并行执行。互斥运算符的示例是 combine_roads_xld 或浇注。
  5. 独家 一组运算符由 HALCON 独占执行,即在执行这样的运算符时,所有其他线程不能调用另一个 HALCON 运算符。示例包括修改 OCR 分类器的所有 OCR/OCV 运算符。
  6. 独立 一组运算符独立于其他运算符执行,甚至是独占运算符。示例都是元组运算符。 如前所述,参考手册中对 HALCON 运算符的描述包含一个名为“并行化”的条目,它指定了使用 HALCON 时的行为。该条目指定了如上所述的可重入级别。

2.2 Style Guide

以下提示通常对多线程编程很有用:

线程数 ≤ 处理器或内核数

如果您使用的线程数多于处理器或内核数,则由于同步开销,您的应用程序实际上可能比以前慢。 请注意,在计算线程时,只有所谓的工作线程是相关的,即连续运行/工作的线程。 局部变量 如果可能,请使用局部变量,即在使用它们的线程中实例化变量。 如果多个线程使用同一个变量,则必须使用适当的并行编程构造(互斥体、信号量;有关详细信息,请参阅您的编程语言的文档)来同步它们对变量的访问。 一个例外是使用所谓的“单线程单元”模式的 COM 应用程序,因为这里的调用是自动同步的。 但是,这种模式还有其他缺点,如下面的 HALCON/COM 提示中更详细地描述。

使用 HALCON 时,请记住以下提示:

初始化

在多线程程序中并行调用 HALCON 运算符之前,您必须独占调用一个运算符。这是允许 HALCON 初始化其内部数据结构所必需的。 I/O 和可视化 请记住,创建或删除文件的操作符是专门工作的,即其他线程必须等待。 程序员必须确保线程不会同时访问同一个文件(或句柄)! 有关不同操作系统上的可视化问题的信息,请参阅第 22 页上的第 2.3 节。

多线程与自动并行化

如果您在多线程程序中明确平衡多个处理器或内核上的负载,我们建议关闭自动并行化机制以获得最佳性能(或减少其使用的线程数,使线程总和不超过处理器或内核的数量)。如何切换自动并行化或减少线程数在第 23 页的 2.4.1 节中描述。 HALCON/COM 请注意,在 COM 应用程序中,线程默认使用所谓的“单线程单元”(STA) 模式创建。在这种模式下,对 COM 对象的调用(以及因此对 HALCON 操作符的所有调用)与 windows 消息队列自动同步。 此外,在此公寓模型中,对 COM 对象的调用始终由实例化该对象的线程执行。因此,如果您在一个线程中实例化 HFramegrabberX 并在另一个线程中调用 GrabImageAsync,则两个线程在图像获取期间都会被阻塞!因此,使用局部变量非常重要,即在使用它们的线程中实例化对象(见上文)。

2.3 多线程操作符

在操作员部分“系统 . 多线程”,HALCON 提供了用于创建和使用同步对象(如互斥锁、事件、条件变量和屏障)的操作符。 使用它们,您可以以独立于平台的方式同步线程。 但是请注意,到目前为止还没有提供用于创建线程的运算符。

2.4 Examples

HALCON 目前为并行编程提供了以下示例(相对于 %HALCONEXAMPLES% 的路径): HALCON/C • c\source\example_multithreaded1.c two threads extract different elements on a board in parallel HALCON/.NET • c#\MultiThreading (C#) 在三个线程中执行图像采集、处理和显示 • hdevengine\c#\MultiThreading (C#) 使用 HDevEngine 由两个线程并行执行相同的 HDevelop 过程 • hdevengine\c#\MultiThreadingTwoWindows (C#) 使用 HDevEngine HALCON/C++ 由两个线程并行执行不同的 HDevelop 过程 • mfc\FGMultiThreading(使用MFC)在两个线程中执行图像采集/显示和处理 • mfc\MultiThreading(使用 MFC) 在三个线程中执行图像采集、处理和显示 • hdevengine\mfc\source\exec_programs_mt_mfc.cpp 使用 HDevEngine 和 MFC 并行执行用于图像采集、数据代码读取和可视化的 HDevelop 程序 • hdevengine\cpp\source\exec_procedures_mt.cpp 使用 HDevEngine 并行执行 HDevelop 程序

3、 图形的线程问题

各种窗口系统对影响 HALCON 图形操作符的多线程有不同的限制。

3.1 Microsoft Windows

在 Microsoft Windows 上,只能从创建窗口的线程访问窗口的消息队列。此外,具有父窗口的窗口必须在创建父窗口的线程中打开。 所有 HALCON 图形操作符都会自动重定向到正确的线程。这是通过向窗口发送特殊消息来完成的。为避免与用户代码冲突,此消息的 ID 是使用 Win32 RegisterWindowMessage 函数动态生成的。对于不是由 HALCON 创建的窗口(这与 open_ext_window 相关,并且在为 open_window 和 open_textwindow 指定父窗口时),(非 HALCON)窗口使用 Win32 SetWindowSubclass 函数自动子类化。对用户代码的唯一要求是,要使该机制起作用,每个窗口都必须有一个活动的消息循环。 为了避免编写用户代码来处理 HALCON 窗口的消息循环,可以指示 HALCON 通过 set_system('use_window_thread','true') 从一个特殊线程创建所有顶级 HALCON 窗口,然后也将关心消息循环。请注意,如果同时使用多个窗口,这可能会对性能产生负面影响,因为 HALCON 仅为所有窗口提供一个线程。

4、附加信息

帮助您在多核或多处理器硬件上使用 HALCON 的附加信息。

4.1 自定义并行化机制

借助 HALCON 的系统参数(可以分别使用运算符 set_system 和 get_system 设置和查询),您可以自定义并行化机制的行为。 您可以通过调用查询处理器(或内核)的数量

get_system('processor_num', Information)

您可以在操作符 set_system 的帮助下关闭 HALCON 的部分功能。 要关闭自动并行化机制,请调用(HDevelop 表示法,有关更多信息,请参阅参考手册)

set_system('parallelize_operators','false')

要切换重入,请调用

set_system('reentrant','false')

当然,您可以通过将“true”作为第二个参数调用 set_system 来再次打开这两种行为。请注意,当关闭重入时,您也会关闭自动并行化,因为它需要重入。 仅当您确定不需要这些功能但想要节省相应的计算开销时才关闭这些功能。例如,如果您编写的顺序程序永远不会在多处理器或多核计算机上运行。 关闭自动并行化机制的一个原因可能是您的多线程程序执行自己的调度并且不希望 HALCON 通过自动并行化进行干扰。请注意,在单处理器或单核计算机上使用 HALCON 时无需关闭自动并行化;如果 HALCON 仅检测到一个处理器或内核,则它会自动执行此操作。 关闭自动并行化时,您可以考虑关闭线程池的使用(请参阅 set_system 的参数“thread_pool”)。 ! 如果已经是这种情况,请不要打开重入! 否则,这将重置并行化系统,包括打开自动操作员并行化。 在手动并行化(多线程)的情况下,这会降低性能。 使用系统参数“parallelize_operators”,您可以更详细地自定义自动并行化机制。 有关更多信息,请参阅 set_system 的说明。 最后,您可以使用参数“thread_num”和“tsp_thread_num”(set_system)来影响用于自动并行化的线程数。 如果您还在程序中执行手动并行化,则减少线程数很有用。 如果永久关闭自动并行化,则还应关闭线程池以节省操作系统资源。

4.2 在多核或多处理器硬件上使用图像采集接口

HALCON 支持的所有图像采集设备都可以在多核或多处理器硬件上使用。 请注意,相应的运算符都不会自动并行化。 大多数算子都是可重入的,只有与设备连接有关的算子(open_framegrabber、info_framegrabber 和 close_framegrabber)在它们的组中被专门处理,即它们阻止其他图像采集算子的并发执行,但与所有算子并行运行 此组之外的非排他运算符(请参阅 open_framegrabber)。 此外,这些运算符是本地的,即在 Windows 下,它们应该从实例化相应对象的线程中调用(参见第 2.1 节)