多線程併發處理的新思路:Parallel的使用

今天偶然接觸到一個新的知識點,雖然.Net 4 裏面早就提出來了,但是我真的不知道,還一直Backgroundwork玩的不亦樂乎,這個Share出來大家共享一下 ~

我順便截了幾個評論過來:

【我在最近的項目中用到了Parallel,原來的操作要10秒,現在4秒左右。不過還是不滿意,後來加上緩存,就變成了0.4秒用Parallel有一個問題,就是CPU佔用非常厲害,必須注意】

啥事緩存?一會查查



From:

___  http://www.cnblogs.com/huangxincheng/archive/2012/04/02/2429543.html ____

我們知道傳統的代碼都是串行的,就一個主線程,當我們爲了實現加速而開了很多工作線程,這些工作線程也就是軟件線程。

在.net 4.0中,微軟給我們提供了一個新的命名空間:System.Threading.Tasks。這裏面有很多好玩的東西,作爲第一篇就介紹下最基礎,最簡單的Parallel的使用。



一: Parallel的使用

在Parallel下面有三個常用的方法invoke,for和forEach。

1:  Parallel.Invoke

    這是最簡單,最簡潔的將串行的代碼並行化。

class Program
{
    static void Main(string[] args)
    {
        var watch = Stopwatch.StartNew();

        watch.Start();

        Run1();

        Run2();

        Console.WriteLine("我是串行開發,總共耗時:{0}\n", watch.ElapsedMilliseconds);

        watch.Restart();

        Parallel.Invoke(Run1, Run2);

        watch.Stop();

        Console.WriteLine("我是並行開發,總共耗時:{0}", watch.ElapsedMilliseconds);

        Console.Read();
    }

    static void Run1()
    {
        Console.WriteLine("我是任務一,我跑了3s");
        Thread.Sleep(3000);
    }

    static void Run2()
    {
        Console.WriteLine("我是任務二,我跑了5s");
        Thread.Sleep(5000);
    }
}



在這個例子中可以獲取二點信息:

第一:一個任務是可以分解成多個任務,採用分而治之的思想。

第二:儘可能的避免子任務之間的依賴性,因爲子任務是並行執行,所以就沒有誰一定在前,誰一定在後的規定了。

 

2:Parallel.for

 我們知道串行代碼中也有一個for,但是那個for並沒有用到多核,而Paraller.for它會在底層根據硬件線程的運行狀況來充分的使用所有的可

利用的硬件線程,注意這裏的Parallel.for的步行是1。

這裏我們來演示一下,向一個線程安全的集合插入數據,當然這個集合採用原子性來實現線程同步,比那些重量級的鎖機制更加的節省消耗。

class Program
    {
        static void Main(string[] args)
        {
            for (int j = 1; j < 4; j++)
            {
                Console.WriteLine("\n第{0}次比較", j);

                ConcurrentBag<int> bag = new ConcurrentBag<int>();

                var watch = Stopwatch.StartNew();

                watch.Start();

                for (int i = 0; i < 20000000; i++)
                {
                    bag.Add(i);
                }

                Console.WriteLine("串行計算:集合有:{0},總共耗時:{1}", bag.Count, watch.ElapsedMilliseconds);

                GC.Collect();

                bag = new ConcurrentBag<int>();

                watch = Stopwatch.StartNew();

                watch.Start();

                Parallel.For(0, 20000000, i =>
                {
                    bag.Add(i);
                });

                Console.WriteLine("並行計算:集合有:{0},總共耗時:{1}", bag.Count, watch.ElapsedMilliseconds);

                GC.Collect();

            }
        }
    }



可以看的出,加速的效果還是比較明顯的。

 

3:Parallel.forEach
    forEach的獨到之處就是可以將數據進行分區,每一個小區內實現串行計算,分區採用Partitioner.Create實現

class Program
    {
        static void Main(string[] args)
        {
            for (int j = 1; j < 4; j++)
            {
                Console.WriteLine("\n第{0}次比較", j);

                ConcurrentBag<int> bag = new ConcurrentBag<int>();

                var watch = Stopwatch.StartNew();

                watch.Start();

                for (int i = 0; i < 3000000; i++)
                {
                    bag.Add(i);
                }

                Console.WriteLine("串行計算:集合有:{0},總共耗時:{1}", bag.Count, watch.ElapsedMilliseconds);

                GC.Collect();

                bag = new ConcurrentBag<int>();

                watch = Stopwatch.StartNew();

                watch.Start();

                Parallel.ForEach(Partitioner.Create(0, 3000000), i =>
                {
                    for (int m = i.Item1; m < i.Item2; m++)
                    {
                        bag.Add(m);
                    }
                });

                Console.WriteLine("並行計算:集合有:{0},總共耗時:{1}", bag.Count, watch.ElapsedMilliseconds);

                GC.Collect();

            }
        }
    }



這裏還是要說一下:Partitioner.Create(0, 3000000)。

第一:我們要分區的範圍是0-3000000。

第二:我們肯定想知道系統給我們分了幾個區? 很遺憾,這是系統內部協調的,無權告訴我們,當然系統也不反對我們自己指定分區個數,

        這裏可以使用Partitioner.Create的第六個重載,比如這樣:Partitioner.Create(0, 3000000, Environment.ProcessorCount),

        因爲 Environment.ProcessorCount能夠獲取到當前的硬件線程數,所以這裏也就開了2個區。

 

下面分享下並行計算中我們可能有的疑惑?

<1> 如何中途退出並行循環?

      是的,在串行代碼中我們break一下就搞定了,但是並行就不是這麼簡單了,不過沒關係,在並行循環的委託參數中提供了一個

ParallelLoopState,該實例提供了Break和Stop方法來幫我們實現。

Break: 當然這個是通知並行計算儘快的退出循環,比如並行計算正在迭代100,那麼break後程序還會迭代所有小於100的。

Stop:這個就不一樣了,比如正在迭代100突然遇到stop,那它啥也不管了,直接退出。

class Program
    {
        static void Main(string[] args)
        {
            var watch = Stopwatch.StartNew();

            watch.Start();

            ConcurrentBag<int> bag = new ConcurrentBag<int>();

            Parallel.For(0, 20000000, (i, state) =>
                  {
                      if (bag.Count == 1000)
                      {
                          state.Break();
                          return;
                      }
                      bag.Add(i);
                  });

            Console.WriteLine("當前集合有{0}個元素。", bag.Count);

        }
    }



<2> 並行計算中拋出異常怎麼處理?

 首先任務是並行計算的,處理過程中可能會產生n多的異常,那麼如何來獲取到這些異常呢?普通的Exception並不能獲取到異常,然而爲並行誕生的AggregateExcepation就可以獲取到一組異常。

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Parallel.Invoke(Run1, Run2);
        }
        catch (AggregateException ex)
        {
            foreach (var single in ex.InnerExceptions)
            {
                Console.WriteLine(single.Message);
            }
        }

        Console.Read();
    }

    static void Run1()
    {
        Thread.Sleep(3000);
        throw new Exception("我是任務1拋出的異常");
    }

    static void Run2()
    {
        Thread.Sleep(5000);

        throw new Exception("我是任務2拋出的異常");
    }
}



<3> 並行計算中我可以留一個硬件線程出來嗎?

  默認的情況下,底層機制會儘可能多的使用硬件線程,然而我們使用手動指定的好處是我們可以在2,4,8個硬件線程的情況下來進行測量加速比。

class Program
    {
        static void Main(string[] args)
        {
            var bag = new ConcurrentBag<int>();

            ParallelOptions options = new ParallelOptions();

            //指定使用的硬件線程數爲1
            options.MaxDegreeOfParallelism = 1;

            Parallel.For(0, 300000, options, i =>
            {
                bag.Add(i);
            });

            Console.WriteLine("並行計算:集合有:{0}", bag.Count);

        }
    }


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章