今天偶然接觸到一個新的知識點,雖然.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);
}
}