【问题标题】:How to atomically increment a static member in IronPython?如何在 IronPython 中原子地增加静态成员?
【发布时间】:2010-02-12 22:11:06
【问题描述】:

我有一个IronPython script,它使用 TPL 和 Parallel.ForEach 使用多个线程处理文件。在 C# 中,我可以使用 Interlocked.Add 和 Interlocked.Increment 在原子线程安全操作中更改全局变量,但这在 IronPython 中不起作用,因为integers are immutable。我目前有一个简单的 Results 类,它将一些变量存储为静态成员,用于跟踪多线程操作的结果。更改多个值时,我可以使用 .NET Monitor 类锁定类以确保更新是线程安全的,但如果我只想更新单个变量(比如只增加 Results.Files),这似乎是一个很大的开销.

我的问题是,是否有更好的方法以类似于 Interlocked.Increment 的工作原理的线程安全或原子方式递增单个静态成员变量(如 IronPython 中的 Results.Files)?或者,是否有任何内置于 python 或 .NET 框架中的线程安全计数器可以用来代替基本整数?

class Results:
    Files = 0
    Lines = 0
    Tolkens = 0 

    @staticmethod
    def Add(intFiles, intLines, intTolkens): 
        #Use the System.Threading.Monitor class to ensure addition is thread safe
        Monitor.Enter(Results) 
        Results.Files += intFiles
        Results.Lines += intLines
        Results.Tolkens += intTolkens
        Monitor.Exit(Results) #Finish thread safe code

【问题讨论】:

  • 我找到了一种解决方法(参见codeblog.theg2.net/2010/02/…),但我仍然有兴趣听到替代方案,或者是否可以使用 IronPython 的 Interlocked 类。

标签: .net multithreading ironpython


【解决方案1】:

看起来python 的方法是使用multiprocessing.Value 对象,默认情况下,每当访问对象时都会锁定该对象。可悲的是,多处理类没有内置到 IronPython 中,因为它基于 CTypes。然而,我确实找到了一种使用 Interlocked 类和对 CLR 对象的引用的方法:

import clr
from System.Threading import Interlocked
refInt = clr.Reference<int>(5) #Create a reference to an integer
#refInt = <System.Int32 object at 0x0000000000000049 [5]>
#refInt.Value = 5
Interlocked.Increment(refInt) #Returns 6 and refInt now points to a new integer
#refInt = <System.Int32 object at 0x000000000000004A [6]>
#refInt.Value = 6

在这种情况下,您可以使用所有 Interlocked 方法来添加、比较、交换、递增和读取 refInt 对象。您也可以直接获取或设置 refInt.Value,但只有 Interlocked 方法是线程安全的。还有 Interlocked 方法 will NOT throw an overflow exception(它只会静默包装),因此请确保您选择的数据类型足够大,不会溢出。

【讨论】:

  • 澄清一下:在原始示例中,您将使用 refFiles = clr.Reference(0) 和 refLines = clr.Reference(0) 替换 Files、Lines 和 Tolkens 变量),然后在您想要更改值时使用 Interlocked 类,并在您想要访问计数器时使用 refLines.Value。
【解决方案2】:

过去,我将工作划分为并行处理,将结果与工作单元一起存储,最后进行整理。想想 Map/Reduce,你就拥有了。

创建一个新线程,在元组进入时将它们吞噬(或等到一切都完成)。这个增量或完整的求和方法应该在最后调用,或者是唯一从队列中读取并递增计数器的方法。

更改 add 方法以将结果作为元组放入队列中。

希望这会有所帮助。

雅各布

【讨论】:

  • 我实际上确实最终使用了一个列表来存储中间结果,方法是调用线程安全的 .append() 方法,然后调用 sum() 或 len() 来获取聚合值(参见 CallCount in链接脚本的 LogWriter 类),但这似乎是一个巨大的 hack,并且对于大量数据或大量变量不能很好地扩展。必须有一种更 Pythonic 的方法来以线程安全的方式执行简单的计数器。
【解决方案3】:

如果你愿意使用一点 C#,你可以创建一个简单的可重用 C# 类,它封装了一个(非静态)int 成员变量并提供了 Interlocked 函数。

class InterlockedWrapper
{
     private int _value;

     public int Increment()
     {
          return Interlocked.Increment(ref _value);
     }
....

等等。然后你就可以在 Python 中使用这个类了。

【讨论】:

  • 我想到了这一点,但是当我看到 Interlocked 采用了 ref 参数时,我寻找一种在 IronPython 中创建引用的方法并找到了解决方案。不过感谢您的帖子!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-10-05
  • 1970-01-01
  • 2015-08-31
  • 1970-01-01
  • 2013-03-28
  • 1970-01-01
相关资源
最近更新 更多