簡體   English   中英

多線程性能提升

[英]Multithread performance boost

有人可以告訴我為什么這些DoCalculation方法中的一種比另一種快得多(例如快40%)嗎?

我有等待設置ManualResetEvents的主線程:

private void LayoutRoot_Loaded(object sender, RoutedEventArgs e)
{
ThreadPool.QueueUserWorkItem((obj) =>
{
    ManualResetEvent[] finishcalc = new ManualResetEvent[] 
    { 
        new ManualResetEvent(false), 
        new ManualResetEvent(false), 
        new ManualResetEvent(false), 
        new ManualResetEvent(false), 
        new ManualResetEvent(false), 
        new ManualResetEvent(false) 
    };
    TimeSpan time1 = new TimeSpan(DateTime.Now.Ticks);
    DoCalculation(rand.Next(10), rand.Next(10), 1, finishcalc[0]);
    DoCalculation(rand.Next(10), rand.Next(10), 2, finishcalc[1]);
    DoCalculation(rand.Next(10), rand.Next(10), 3, finishcalc[2]);
    DoCalculation(rand.Next(10), rand.Next(10), 4, finishcalc[3]);
    DoCalculation(rand.Next(10), rand.Next(10), 5, finishcalc[4]);
    DoCalculation(rand.Next(10), rand.Next(10), 6, finishcalc[5]);

    if (WaitHandle.WaitAll(finishcalc))
    {            
        TimeSpan time2 =new TimeSpan(DateTime.Now.Ticks);
        AddTextAsync(string.Format("DoCalculation Finish in {0}\n" ,(time2-time1).TotalSeconds));
    }
});
}

然后,我有一個創建另一個線程以按順序進行一些計算的方法,這就是我需要前一個線程的結果繼續下一個操作,我發現了兩種方法可以做到這一點,這是針對Silverlight的。

在第一個示例中,我正在創建一個新線程,它等待每個連續的計算完成后再繼續:

void DoCalculation(int number1, int number2, int callid, ManualResetEvent calcdone)
{
    ThreadPool.QueueUserWorkItem((obj0) =>
    {
        AddTextAsync(string.Format("The values for Callid {0} are {1} and {2}\n", callid, number1, number2));
        int result = 0;
        ManualResetEvent mresetevent = new ManualResetEvent(false);
        ThreadPool.QueueUserWorkItem((obj) =>
        {
            result = number1 + number2;
            mresetevent.Set();
        });
        mresetevent.WaitOne();
        mresetevent.Reset();
        ThreadPool.QueueUserWorkItem((obj2) =>
        {
            result *= result;
            mresetevent.Set();
        });
        mresetevent.WaitOne();
        mresetevent.Reset();

        ThreadPool.QueueUserWorkItem((obj2) =>
        {
            result *= 2;
            mresetevent.Set();
        });
        mresetevent.WaitOne();
        AddTextAsync(string.Format("The result for Callid {0} is {1} \n", callid, result));
        calcdone.Set();
    });
}

DoCalculation的第二個示例,我使用一個類作為鏈接,將Action作為參數傳遞給ThreadPool,並將其用作回調以在鏈中創建第二個和第三個線程:

鏈接類:

public class CalcParams
{
    public int CallID;
    public ManualResetEvent ManualReset;
    public int Result;
    public Action<int, ManualResetEvent, int> CallbackDone;
}

異步服務的示例:

public static void DownloadDataInBackground(CalcParams calcparams)
{
    WebClient client = new WebClient();
    Uri uri = new Uri("http://www.google.com");
    client.DownloadStringCompleted += (s, e) =>
    {
        CalcParams localparams = (CalcParams)e.UserState;
        localparams.CallbackDone(e.Result.Length + localparams.Result, localparams.ManualReset, localparams.CallID);
    };
    client.DownloadStringAsync(uri, calcparams);
}

以及改進的doCalculation方法:

void DoCalculation(int number1, int number2, int callid, ManualResetEvent calcdone)
{
    ThreadPool.QueueUserWorkItem((obj0) =>
    {
        int result = number1+number2;
        doCalculationService.DownloadDataInBackground(new CalcParams()
        {
            Result = result,
            ManualReset = calcdone,
            CallID = callid,
            CallbackDone = (r, m, i) =>
            {
                int sqrt = r * r;
                doCalculationService.DownloadDataInBackground(new CalcParams()
                {
                    Result = sqrt,
                    CallID = i,
                    ManualReset = m,
                    CallbackDone = (r2, m2, i2) =>
                    {
                        int result2 = r2 * 2;
                        AddTextAsync(string.Format("The result for Callid {0} is {1} \n", i2, result2));
                        m2.Set();
                    }
                });
            }
        });
    });
}

謝謝。

沒有充分的理由調用ThreadPool.QueueUserWorkItem ,然后立即等待它完成。 也就是說,編寫此代碼:

ThreadPool.QueueUserWorkItem(() =>
    {
        // do stuff
        mevent.Set();
    });
mevent.WaitOne();

不會給您任何好處。 您的主線程最終等待。 實際上,這比僅僅編寫還要糟糕:

// do stuff

因為線程池必須啟動一個線程。

您可以通過刪除所有嵌套的“異步”工作來簡化並加快您的第一個DoCalculation方法:

void DoCalculation(int number1, int number2, int callid, ManualResetEvent calcdone)
{
    ThreadPool.QueueUserWorkItem((obj0) =>
    {
        AddTextAsync(string.Format("The values for Callid {0} are {1} and {2}\n", callid, number1, number2));
        int result = 0;

        result = number1 + number2;
        result *= result;
        result *= 2;

        AddTextAsync(string.Format("The result for Callid {0} is {1} \n", callid, result));
        calcdone.Set();
    });
}

根據最新問題進行編輯

您的新示例#3在某種程度上簡化了操作,但仍然沒有抓住重點。 當您執行新的DoCalculation方法時,將發生以下情況:

  1. ThreadQueue.QueueUserWorkItem創建一個新線程, DoCalculation方法退出。 現在,您正在運行一個后台線程。 我們將其稱為線程1。
  2. 該代碼調用DownloadDataInBackground 該方法啟動另一個線程以異步方式下載數據。 調用該線程2。
  3. 線程1退出。
  4. 線程2完成下載后,將調用完成回調,該回調再次調用DownloadDataInBackground 創建線程3,開始執行,然后線程2退出。
  5. 線程3完成下載后,它將調用完成回調,進行計算,輸出一些數據並退出。

因此,您啟動了三個線程。 從來沒有任何有意義的“多線程”正在進行。 也就是說,在任何時候都沒有一個以上的線程在從事有意義的工作。

您的任務是按順序執行的,因此沒有必要啟動多個線程來運行它們

如果您只是編寫以下代碼,則代碼將更加簡潔,執行速度會更快(由於不必啟動太多線程):

ThreadPool.QueueUserWorkItem((obj0) =>
{
    DownloadString(...); // NOT DownloadStringAsync
    DownloadString(...);
    // Do calculation
});

一個線程按順序執行每個任務。

唯一需要多個線程的情況是,是否要同時執行多個任務。 顯然,這不是您在做什么。 實際上,您的問題是:

然后,我有一個創建另一個線程以按順序進行一些計算的方法,也就是說,我需要前一個線程的結果才能繼續下一個線程。

順序任務意味着一個線程。

我是否可以建議您將反應性擴展(Rx)視為在Silverlight中使用多線程的另一種方法?

這是您在Rx中完成的代碼:

Func<int, int, int> calculation = (n1, n2) =>
{
    var r = n1 + n2;
    r *= r;
    r *= 2;
    return r;
};

var query =
    from callid in Observable.Range(0, 6, Scheduler.ThreadPool)
    let n1 = rand.Next(10)
    let n2 = rand.Next(10)
    from result in Observable.Start(() => calculation(n1, n2))
    select new { callid, n1, n2, result };

query.Subscribe(x => { /* do something with result */ });

它自動將計算結果推送到線程池中-我將Scheduler.ThreadPool參數放入其中,但這是SelectMany查詢的默認設置。

使用這種代碼,您通常不必擔心所有的MRE,並且可以很容易地閱讀代碼,並且可以更輕松地對其進行測試。

Rx是受支持的Microsoft產品,可在桌面CLR和Silverlight上運行。

這是Rx的鏈接:

哦,我認為您之所以會獲得非常不同的性能結果,是因為Silverlight僅具有毫秒級的計時分辨率,因此您實際上必須進行數千次計算才能獲得良好的平均值。


編輯 :根據注釋中的請求,這是一個使用Rx鏈接每個中間計算結果的示例。

Func<int, int, int> fn1 = (n1, n2) => n1 + n2;
Func<int, int> fn2 = n => n * n;
Func<int, int> fn3 = n => 2 * n;

var query =
    from callid in Observable.Range(0, 6, Scheduler.ThreadPool)
    let n1 = rand.Next(10)
    let n2 = rand.Next(10)
    from r1 in Observable.Start(() => fn1(n1, n2))
    from r2 in Observable.Start(() => fn2(r1))
    from r3 in Observable.Start(() => fn3(r2))
    select new { callid, n1, n2, r1, r2, r3 };

當然,三個lambda函數可以很容易地成為常規方法函數。

如果您具有使用BeginInvoke / EndInvoke異步模式的函數,則另一種替代方法是使用FromAsyncPattern擴展方法,如下所示:

Func<int, int, IObservable<int>> ofn1 =
    Observable.FromAsyncPattern<int, int, int>
        (fn1.BeginInvoke, fn1.EndInvoke);

Func<int, IObservable<int>> ofn2 =
    Observable.FromAsyncPattern<int, int>
        (fn2.BeginInvoke, fn2.EndInvoke);

Func<int, IObservable<int>> ofn3 =
    Observable.FromAsyncPattern<int, int>
        (fn3.BeginInvoke, fn3.EndInvoke);

var query =
    from callid in Observable.Range(0, 6, Scheduler.ThreadPool)
    let n1 = rand.Next(10)
    let n2 = rand.Next(10)
    from r1 in ofn1(n1, n2)
    from r2 in ofn2(r1)
    from r3 in ofn3(r2)
    select new { callid, n1, n2, r1, r2, r3 };

前面有點凌亂,但查詢有點簡單。

注意: Scheduler.ThreadPool參數也是不必要的,但僅包含它即可明確顯示查詢是使用線程池執行的。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM