【问题标题】:Need help reducing CPU usage of multi-threaded VB.net program需要帮助减少多线程 VB.net 程序的 CPU 使用率
【发布时间】:2017-06-16 15:01:30
【问题描述】:

我在学习 vb.net 时一直在做一个项目:一个多线程代理检查器。我让它工作,并且在小型测试(1000 个要检查的代理列表)上它工作得很好。但是,我想用它来检查 500,000 个或更多代理的列表。当我尝试这样做时,我看到大量的 CPU 使用率。我有一个 16GB 内存的 AMD FX-8320,仅供参考。

我的所有代码都可以在我的 Github (click this to visit) 上查看,但是我将在这里复制主要的重要部分。

基本流程:

  1. 用户点击“开始”,每个线程开始 “线程代理检查器()”
  2. threadedProxyChecker() 遍历包含从文本文件加载的所有代理的 List(Of String) 的所有成员
  3. 每个线程正在测试的代理被加载到一个临时 List(Of String) 中,因此工作不会被完成两次,并且这个 List(Of String) 受 SyncLock 保护。调用“checkProxy(proxy)”,然后从临时 List(Of String) 中删除代理。
  4. 将结果记录到 l1 表示工作或 l2 表示失败。 (可能不需要 l2,只是存储在 int 中的所有失败的计数?)
  5. “performStep()”更新 UI 以在 ListBox 中显示工作代理,递增 ProgressBar,并报告完成百分比以及 Label 中的工作/无响应计数。
  6. 当每个线程到达列表末尾时,将工作/无响应代理的总数与列表大小进行比较,作为程序结束的条件。 Thread.Abort() 在所有工作完成后被调用(我知道这很糟糕,但我不确定我还能怎么做)

我如何检查每个代理:

Function checkProxy(proxy As String) As Boolean
    Dim myProxy As WebProxy
    Dim Temp As String
    Try
        myProxy = New WebProxy(proxy)
        Dim r As HttpWebRequest = HttpWebRequest.Create("http://azenv.net")
        r.UserAgent = "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/29.0.1547.2 Safari/537.36"
        r.Timeout = 3000
        r.Proxy = myProxy
        Dim re As HttpWebResponse = r.GetResponse()
        Dim rs As Stream = re.GetResponseStream
        Using sr As New StreamReader(rs)
            Temp = sr.ReadToEnd()
        End Using
        Dim Text = Temp
        rs.Dispose()
        rs.Close()
        r.Abort()
        If Text.Contains("HTTP_HOST = azenv.net") Then
            If Text.Contains("REQUEST_TIME =") Then
                Return True
            End If
        Else
            Return False
        End If
    Catch ex As Exception
        Return False
    End Try
    Return False
End Function

每个线程执行的主要代码:

Private Sub threadedProxyChecker()
        Dim counter As Integer = 0
        For Each proxy As String In proxies
            SyncLock curProxLock
                If tmpProx.Contains(proxy) Then
                    GoTo Skip
                Else
                    tmpProx.Add(proxy)
                End If
            End SyncLock
            If Not l2.Contains(proxy) Then
                If Not l1.Contains(proxy) Then
                    If (checkProxy(proxy)) Then
                        performStep(True, proxy)
                        l1.Add(proxy)
                        SyncLock curProxLock
                            tmpProx.Remove(proxy)
                        End SyncLock
                    Else
                        performStep(False, proxy)
                        l2.Add(proxy)
                        SyncLock curProxLock
                            tmpProx.Remove(proxy)
                        End SyncLock
                    End If
                End If
            End If
Skip:
        Next
        If proxies.Count() <= (l1.Count() + l2.Count()) Then
            If Not isBox Then
                SyncLock indexLock
                    MessageBox.Show("Done checking!" & vbNewLine & l1.Count() & " working proxies")
                    isBox = True
                End SyncLock
                Label5.Invoke(Sub()
                                  Label5.Text = "Working: " & l1.Count()
                                  Label5.Update()
                              End Sub)
                Label4.Invoke(Sub()
                                  Label4.Text = "Unresponsive: " & l2.Count()
                                  Label4.Update()
                              End Sub)
            End If
        End If
        Thread.CurrentThread.Abort()
    End Sub

线程是如何启动的:

 Private Sub Button4_Click(sender As Object, e As EventArgs) Handles Button4.Click
    isBox = False
    Dim threadCount As Integer = TrackBar1.Value

    For int As Integer = 1 To threadCount Step 1
        d(int.ToString) = New Thread(AddressOf threadedProxyChecker)
        d(int.ToString).IsBackground = True
        d(int.ToString).Start()
    Next
End Sub

“threadedProxyChecker()”调用的“performStep()”方法

Function performStep(bool As Boolean, proxy As String)
    If bool Then
        ListBox2.Invoke(Sub()
                            ListBox2.Items.Add(proxy)
                            ListBox2.TopIndex = ListBox2.Items.Count - 1
                            ListBox2.Update()
                            Label5.Text = "Working: " & l1.Count()
                            Label5.Update()
                        End Sub)
    Else
        Label4.Invoke(Sub()
                          Label4.Text = "Unresponsive: " & l2.Count()
                          Label4.Update()
                      End Sub)
    End If

    count = count + 1

    ProgressBar1.Invoke(Sub()
                            ProgressBar1.PerformStep()
                            ProgressBar1.Update()
                        End Sub)

    Label1.Invoke(Sub()
                      Dim percent As Double = Math.Round((count / proxies.Count() * 100), 2, MidpointRounding.AwayFromZero)
                      Label1.Text = "Progress: " & count & "/" & proxies.Count() & " checked " & "(" & percent & "%)"
                      Label1.Update()
                  End Sub)
    Return True
End Function

任何关于如何使工作更顺利和/或如何降低 CPU 使用率的建议都很棒!谢谢 :) -埃里克

【问题讨论】:

  • 永远不要调用Thread.CurrentThread.Abort() - 唯一的例外是如果你试图让你的程序崩溃并且你想结束所有线程。调用 .Abort() 可能会破坏 .NET 运行时状态,之后您无法依赖它正常运行。
  • 还有一个很好的机会是你正在用所有的.Invoke 调用来杀死你的 CPU。你想尽可能地避免它们。您应该将数据从 UI 线程、后台线程上的进程编组,然后将数据编组回 UI 线程一次。
  • @Enigmativity 那么我应该如何终止我的线程呢?到达该方法的末尾时会自动终止吗?
  • 我的想法是这样的:pastebin.com/JGR8Gzii
  • 是的,它们会在启动方法结束时自动结束。请记住,启动线程的成本很高,因此不要仅仅为少量工作启动线程。

标签: vb.net multithreading


【解决方案1】:

执行重复性任务的线程应在其循环中的某处Sleep 以“让出”处理时间给其他线程。

在循环中的Next 语句之前放置一个Sleep(1) 语句。

【讨论】:

    【解决方案2】:

    线程有两个主要用途。

    1. 在后台工作以保持 UI 响应式
    2. 并行执行更多工作

    从您的代码来看,我会说您的目标是 (2),这意味着您将增加 CPU 负载(这通常是一件好事,浪费了空闲的 CPU)。如果您的代码使用过多的 CPU,那么也许您可以考虑降低可执行文件的优先级。

    'Process Priority
    Dim CurrentProcess As Process = Process.GetCurrentProcess
    CurrentProcess.PriorityClass = ProcessPriorityClass.BelowNormal
    'Thread Priority
    Dim CurrentThread As Thread = Thread.CurrentThread
    CurrentThread.Priority = ThreadPriority.BelowNormal
    

    它仍然会使用相同数量的 CPU,但它会更好地让给其他进程。

    如果你想稍微优化你的代码,我建议你有一个 URL:s 列表来检查,然后你的线程在索引上做一个 SyncLock 来选择。像这样的东西:

    Dim ProxyList As New List(Of String) ' The list of URL:s
    Dim Index As Integer = 0 ' The index to use by next thread
    Dim IndexObject As New Object ' The SyncLock object
    
    ' In the thread
    Dim Value As String ' The URL we get
    SyncLock IndexObject
        If Index >= ProxyList.Count Then Return ' We are at the end, we should bail out
        Value = ProxyList(Index) ' Get the value
        Index += 1 ' Increment the counter
    End SyncLock
    

    现在,每个线程都会执行大量 SyncLock 和查找,这可能会减少 CPU 占用率。

    【讨论】:

    • 主线程将工作分配给工作线程不是更好吗,每个工作线程都指向一个特定的代理?然后你可以完全切断源数据结构上的锁定(避免潜在的争用,我不确定这是否会成为一个问题,因为我不确定工作通常需要多长时间),你只需要担心在工人完成后合并结果。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-01-12
    相关资源
    最近更新 更多