简单线程崩溃课程?

时间:2011-06-05 20:38:36

标签: .net vb.net multithreading parallel-processing task-parallel-library

我正在测试线程的想法,但现在只在非常关键的位置。线程为几乎任何东西添加了一个非常迷人的复杂程度,但是对于.NET,System.Threading中的线程似乎有很多选择。我想知道哪个是最好的处理字符串操作。

考虑将复杂字符串提供给自定义对象。该对象当前在某个时刻拆分字符串,并将第一部分提供给函数,然后当该函数完成时,将另一半字符串提供给第二个函数。这两个函数之间没有依赖关系,因此应该是线程的良好候选者,以便两个函数可以在字符串的每个部分上同时工作。

领奖前的例子:

Public Sub ParseString(ByVal SomeStr As String)
    If String.IsNullOrWhitespace(SomeStr) Then
        Throw New ArgumentNullException("SomeStr")
    End If

    ' Assume that ParsedFirstString is a boolean that is set to
    ' True if the call to ParseFirstString completes successfully.
    ' Ditto for ParsedSecondString.

    Dim MyDelimiter As Char = "|"c
    Dim SomeStrArr As String() = SomeStr.Split({MyDelimiter}, 2)

    Call Me.ParseFirstString(SomeStrArr(0))

    If Me.ParsedFirstString = False Then
        Throw New ArgumentException("Failed to parse the first part of the string.")
    End If

    Call Me.ParseSecondString(SomeStrArr(1))

    If Me.ParsedSecondString = False Then
        Throw New ArgumentException("Failed to parse the second part of the string.")
    End If
End Sub


这很好用,在我的多核系统上的定时循环内测试,我可以在~140ms-170ms内执行1000次(平均〜1,200ms +如果10,000次) 。这是一个可以接受的速度,如果我不能让线程发挥得很好,那么我会继续前进。但是我在查看一个threading example之后尝试了一种线程方法,并在调用thread with parameters时提出了一个SO问题,并且编写了类似于以下内容的代码:

Public Sub ParseString(ByVal SomeStr As String)
    If String.IsNullOrWhitespace(SomeStr) Then
        Throw New ArgumentNullException("SomeStr")
    End If

    Dim MyDelimiter As Char = "|"c
    Dim SomeStrArr As String() = SomeStr.Split({MyDelimiter}, 2)

    Dim FirstThread As New Thread(Sub() Me.ParseFirstString(SomeStrArr(0))
    Dim SecondThread As New Thread(Sub() Me.ParseSecondString(SomeStrArr(1))

    FirstThread.Priority = ThreadPriority.Highest
    SecondThread.Priority = ThreadPriority.Highest

    Call FirstThread.Start()
    Call SecondThread.Start()

    If Me.ParsedFirstString = False Then
        Throw New ArgumentException("Failed to parse the first part of the string.")
    End If

    If Me.ParsedSecondString = False Then
        Throw New ArgumentException("Failed to parse the second part of the string.")
    End IF
End Sub


这个问题是解析字符串的第一部分或第二部分可以在两个完成之前完成两个例外。所以我进一步环顾四周,发现我可以使用Join方法等待两个线程完成。这解决了异常的绊倒,但它大大增加了执行时间。执行上述功能1,000次并立即计时产生的平均运行时间最多可达〜3,700ms。看起来线程似乎不适合这种任务。

但似乎还有其他线程机制,包括ThreadPools和BackgroundWorkers。可能是其他人我还没有抬头(几小时前我刚刚开始搞乱这个)。

社群对此类任务线程的意见是什么?我第一次尝试线程有什么问题?

仅供参考,我不会更新任何UI组件,也不会将结果写入任何类型的存储介质。





结论:

看来我的字符串解析功能比我想象的要好很多。尝试了Parallel ClassTask Class,如果我测试10,000次迭代循环,然后单线程,我的测试数据大约是1,220ms-1,260ms。如果我实现甚至基本Parallel.Invoke()将解析分成两个并行线程,我将该时序循环填充到另外~300ms(可能是由于匿名委托的开销,但似乎没有办法围绕这个)。这是在Core2 Q9550 Yorkfield上,而不是超频的95W处理器,用于比较。

获胜的选择是为这个特定的代码区域保持单线程。感谢所有参与者!

3 个答案:

答案 0 :(得分:2)

当正在执行的工作占用的CPU周期多于创建/管理/连接多个线程的开销所占用的CPU周期时,线程(或一般的并行化)是有益的。

如果您正在解析代码&字符串相对简单,多线程实际上会使事情变慢。

线程创建是一项相对昂贵的操作。 .NET 4引入了“任务”的概念。任务是您希望与其他代码并行执行的代码块。 .NET Framework内置了许多智能功能,可以将所有任务分成理想数量的线程(通常与您拥有的CPU核心数相同),并为多个任务重用相同的线程。

任务仍然有非常重要的开销,但比原始线程要少得多。因此,在许多情况下,任务仍会比串行代码慢,但这些情况更少。如果没有看到您的输入字符串和解析方法,我们就无法说出您的特定场景适合此频谱的位置。

答案 1 :(得分:2)

我建议使用像ParallelTask这样的TPL类。

无论您的代码是否受益于并行执行,您都需要对特定计算机进行基准测试并找出答案。这是最好的方法。相同的代码可以减慢一台机器上的执行速度,但在另一台机器上加速很多。基本上取决于CPU(内核数量,超线程等),算法和并行任务数量。

如果您使用TPL,您的代码看起来就像:

    Call Parallel.Invoke(
        Sub()
            Me.ParseFirstString(SomeStrArr(0))
        End Sub,
        Sub()
            Me.ParseFirstString(SomeStrArr(1))
        End Sub)

对不起,我不擅长VB.NET语法。可能有一种方法可以缩短它。

答案 2 :(得分:0)

对我来说,从UI线程卸载任何计算是最好的选择。特别是如果由于任何原因它可能是耗时的,无论是复杂程度,还是两者的母亲。

正如您所发现的,有很多方法可以做到这一点。很大程度上取决于你/之后想要的信息类型。

虽然我的.net选择是c#,但前提是相同的。

您可以使用任务,这些任务看起来效果很好,因为您可以轻松地为它们提供选项 Parallel.Invoke允许选项

对我来说是一个后台工作者,除非你确切地知道你将要有多少,否则我发现它们使用起来较少,因为如果你使用工具箱中的组件,你必须预先制作它们,你还需要如果他们在忙碌之前做好工作,然后再将他们的工作做好。就个人而言,我想要一些我可以说的话“在这里,去做这个”就像一群小兵在排队等候工作一样。

线程也可以工作,但是,如果(像我一样)你发送大约10k个电子邮件并要求它解析它们,我发现它更复杂一些。

从它的外观来看,你有最好的想法,拿出你想要的代码,然后尝试每种方法,看看哪种方法适合你,你的思维方式和速度。如果你发现一个比其他任何一个慢得多,或者执行它的UI线程,那么很可能有一种方法可以改进它。

当然,如果你被困在例子上,MS当然会有各种各样的例子来帮助你。