Excel 2007 中的多线程重计算


Excel 2007 是第一个使用了多线程工作表函数重计算的版本。最多你可以设置使用 1024 个并发线程进行重计算。而不用考虑计算机上的 CPU数量 或 CPU核心数量。

注意: 操作系统系统开销与线程数有着密切的关系,因此你不要把线程数量设置过多。

如果计算机使用了多个处理器或是多核处理器,操作系统负责以最有效的方式分配线程。

Excel 2007 MTR 概要

Excel 会尝试在不同的线程上同时对计算链进行计算。下图是一个十分简单的计算链。

假设所有单元格中的函数都是线程安全的条件下。当 A1 计算完后,A2 然后 A3 在另一个线程上完成计算,B1 然后 C1也会在一个不同的线程上完成计算。


注释 线程安全单元格(thread-safe cell) 表示单元格中只包含了线程安全函数。


通常 workbook 包含了比以上实例复杂的多的关系树。而且,单元格的重计算时间,在计算完成以前是无法知道的,同时函数计算时间与它的参数有着密切的关系。为了提升计算的效率,Excel 会尝试在每次计算时先优化计算的顺序,直到无法继续优化为止。

Excel 2007 使用单个的主线程执行以下操作:

  • 运行 Excel 内部命令
  • 运行 XLL 命令
  • 运行 XLL Add-in 管理接口函数 (如:xlAutoOpen 函数,等)
  • 运行 VBA 中的用户定义命令(也就是宏命令)。
  • 运用 VBA 用户定义函数。
  • 不是线程安全的工作表函数。
  • COM add-in 命令和函数。
  • 条件格式表达式中的函数和操作
  • 用于公式中定义的名称定义的函数和操作
  • 在公式编辑框中使用 F9 键强制评估一个表达式。

所有工作表公式,无论是否是线程安全的,都是在主线程上进行评估操作,除非 Excel 2007 中设置了使用多个线程处理。如果用户设置了使用多个线程,那么添加的线程将会用于线程安全单元格。注意此时主线程出于负载平衡方面的考虑,仍然会用于线程安全单元格。

这儿需要重申的是,Excel 一次只能运行一个命令 ( command 而不是 function )。因此对 command 来说,你不用考虑创建线程安全函数时的那些防范措施。如,线程局部内存和临界段。

Excel 2007 线程安全性考虑

Excel 2007 只需要在以下几种情况中考虑线程安全性:

  • Excel中的一元操作和位操作。
  • Excel 2007 中几乎所有的内置工作表函数。
  • XLL add-in 明确被注册为线程安全的函数。

下面的 Excel 2007 内置函数是非线程安全:

  • PHONETIC
  • CELL 当 format 或 address 参数被使用时。
  • INDIRECT
  • GETPIVOTDATA
  • CUBEMEMBER
  • CUBEVALUE
  • CUBEMEMBERPROPERTY
  • CUBESET
  • CUBERANKEDMEMBER
  • CUBEKPIMEMBER
  • CUBESETCOUNT
  • ADDRESS 第50个参数给了任何一个数据库函数(DSUM、DAVERAGE等等),引入到了关键表
  • ERROR.TYPE
  • HYPERLINK

以下几种情况已经明确了不是线程安全的。

  • VBA 用户定义函数
  • COM add-in 用户定义函数
  • XLM 宏表用户定义函数
  • XLL add-in 没有被注册为线程安全的函数

下面的操作和函数不是线程安全的,如果调用一个注册为线程安全的 XLL 函数,会提示操作失败。

  • 调用 XLM 信息函数,如果 xlfGetCell ( GET.CELL )
  • 调用 xlfSetName ( SET.NAME ) 定义和删除 XLM 内部名称
  • 使用 xlUDF 调用非线程安全的用户定义函数。
  • 为包含了非线程安全函数或是包含了非线程安全函数的定义名称使用 xlfEvaluate 函数。
  • 使用 xlAbort 函数清除一个中断条件。
  • 调用 xlCoerce 函数获取一个非计算单元格引用值。

注意: XLL 工作表函数不允许调用 C-API 命令,例如,xlcSave、不管它是否被注册为线程安全的。


考虑到 XLL 函数声明为线程安全的,不能调用 XLM 信息函数或引用非计算单元格,Excel 2007 不允许 XLL 函数注册为宏表等效线程安全函数。因此,打算使用 xlCoerce 获取非计算单元格引用,会失败返回 xlretUncalced 错误。调用 XLM 信息函数会返回 xlretFailed 错误。其它错误点,参考先前在 Excel 2007 C API ,xlretNotThreadSafe 中介绍的错误代码。

以下的 只用于 XLL中的 C API 回调函数都是线程安全的

  • xlCoerce (except although coercion of uncalculated cell references fails)
  • xlFree
  • xlStack
  • xlSheetId
  • xlSheetNm
  • xlAbort (except when used to clear a break condition)
  • xlGetInst
  • xlGetHwnd
  • xlGetBinaryName
  • xlDefineBinaryName

唯一的例外是 xlSet 函数,它在任何情况下,都不能从任何工作表函数中调用。

所有的 XLL工作表函数,都可以注册为 Excel 线程安全函数。也就是说 Excel 可以同时在多个线程上安全的使用它们。如果函数不是线程安全的,但你又注册为线程安全的,就会影响到 Excel 的稳定性。

将 XLL 函数注册为线程安全的。

编写线程安全函数,开发者必需遵守以下规则:

  • 不要调用其它 DLL 中的资源,这些资源可以是非线程安全的。
  • 不要通过 C API 或 COM 做任何不安全的线程调用。
  • 使用临界段保护多线程访问下的资源。
  • 使用线程专有的本地内存,来代替静态变量。

Excel 强加了额外的限制: 线程安全函数不能注册为函数表函数,因此不能调用 XLM 信息函数 或 非计算单元格的值。

内存争用

多线程系统必需解决两个基本问题:

  • 如何在多个线程读写内存时,保护内存。
  • 如何创建和访问线程的私有内存。

Windows 操作系统和 Windows 开发包(SDK ),为解决以上问题,分别提供了两个工具:临界段 和 线程本地存储(TLS) API。

当两个工作表函数,需要访问和编辑一个DLL中全局变量,第一个问题就会出现,大家要记住的是,这样一个全局变量,可能隐藏在全局的类实例中。

当一个工作表函数在函数体内代码声明了一个静态变量或对象。C/C++ 编译器将只为所有的线程创建一个内存空间。这个意思简单来说,就是多个不同的线程将会争用同一个变量内存。并随意对同一个变量内容进行修改。

MTR 的实例应用

任何 XLL 输出的函数,都可以使用 Excel 2007 中的多线程重计算技术,这些函数不需要执行线程不安全操作。 这能让 Excel 快速重计算工作簿。

特别是,MTR 会极大缩减工作表中 UDF 函数的重计算时间。尤其是,考虑到 UDF 调用远程服务,它会同时处理多个请求,并且这个 函数 关联了很多单元格。如果是单线程的重计算,每次调用 UDF,访问 远程服务,必需等前一个调用完成操作后,才能执行这一次操作。这浪费了许多的处理能力。如果重计算使用的多线程,Excel 可在同一时间执行多次调用。

如果 Excel 配置使用与N个线程,并且工作表的依赖树允许使用这N个纯种,那么重计算时间理论上可以减少到 单线程计算时间的 1/N 。即使计算机是单处理器的,这也是有可能的,

每个附加的线程都是增加操作系统的开销,因此,需要进行一些测试,找到合适的线程数量。

例如,单线程处理器计算机,正在运行 Excel 2007,工作簿包含了 1000 个单元格。Excel 使用了UDF,调用了一个或多个远程服务。假设这 100个单元格都是各自独立的,相互之间没有依赖性,因此 Excel 在调用下下服务之前,不需要等着前一个调用完成。如果一个服务可以同时处理100个请求,Excel 配置使用100个线程,执行时间会减少到仅仅只有单线程处理时间的 1/100 ,系统总的开销关系着 Excel 分配的每个线程。这意味着操作系统管理 100 个线程,在实践中减少的计算次数,并没有如此之多。这还有一个隐含的服务尺度假设,让它处理100个并发任务不会显著影响单个任务完成时间。

在程序开发中,这种技术对 蒙特卡罗方法, 拥有十分重要的意义,在其它的一些数值密集型任务中,可以拆成很多子任务,发送给服务器处理。

Excel 服务注意事项

Excel 服务是 Office 2007 新发布的服务技术。它支持在服务器上,载入、计算、渲染 Excel 电子表格。然后用户可以使用标准的 浏览器工具查看表格以及与它进行交互。

Excel 服务 UDF 使用 .NET 技术创建。XLL 不支持 Excel 服务。托管代码 UDF 资源 可以访问所有 XLL 函数,因此,用户可以使用于客户端工作簿一样的函数。

通过这种方法,我们就可使用 XLL 函数,因此它们必需将参数和返回值的数据类型从本地类型转换为 .NET 类型。NET 会为每个 XLL 函数的访问输出一个 UDF 服务。另外还要求这种方式调用的 XLL 函数必需是线程安全的。因为 XLL 函数并不是使用 本地 Excel 的注册方式来注册 XLL。NET 没有办法确定线程的安全性。这是 XLL 开发人员必需解决的问题 。


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM