.Net 4.0并行库实用性演练
前面說(shuō)在練習(xí)Parallel時(shí),發(fā)現(xiàn)另有乾坤,是這樣的代碼:
代碼 static IEnumerable<Person> testFill(){
var list =new List<Person>(9);
Enumerable.Range(1, 99999).ToList().ForEach(n =>
{
var name ="Person"+ n %9;
list.Add(new Person { Id = n, Name = name });
});
Console.WriteLine("Person's count is {0}", list.Count);
return list;
}
static IEnumerable<Person> testFillParallel()
{
var list =new List<Person>(9);
Enumerable.Range(1, 99999).AsParallel().ForAll(n =>
{
var name ="Person"+ n %9;
list.Add(new Person { Id = n, Name = name });
});
Console.WriteLine("Person's count is {0}", list.Count);
return list;
}
class Person
{
internalint Id { get; set; }
internalstring Name { get; set; }
}
試驗(yàn)結(jié)果如下(單位ms):
| ?次數(shù) | ?1 | ?2 | ?3 | ?4 |
| ?Fill 方法 | ?37 | ?27 | ?26 | ?26 |
| ?FillParallel 方法 | ?43 | ?20 | ?19 | ?20 |
這個(gè)結(jié)果有點(diǎn)奇妙的。第一次多線(xiàn)程居然還不如單線(xiàn)程快,和上文例子比較一下,有點(diǎn)明白了。稍微改了下代碼,在Add語(yǔ)句前加了個(gè)Thread.Sleep(1),并把 List<Person>集合元素減為999,試了一次,結(jié)果如下(單位ms):
| ?次數(shù) | ?1 | ?2 | ?3 | ?4 |
| ?Fill 方法 | ?1012 | ?998 | ?998 | ?999 |
| ?FillParallel 方法 | ?547 | ?504 | ?504 | ?504 |
多個(gè)線(xiàn)程協(xié)同工作時(shí),分配任務(wù)本身有開(kāi)銷(xiāo),要是分配的開(kāi)銷(xiāo)比任務(wù)本身還大,多線(xiàn)程就沒(méi)有意義了。就比如你交待別人做某件事,要是交待的功夫比自己做還長(zhǎng),還不如自己做。不過(guò)從結(jié)果也可以看出一個(gè)辯證關(guān)系,從長(zhǎng)遠(yuǎn)打算,第一次讓別人熟悉業(yè)務(wù),付出點(diǎn)培訓(xùn)成本,執(zhí)行完一次后,以后就輕松多了,速度提高了一倍。如果這里Sleep一下,模擬長(zhǎng)一點(diǎn)的單次處理過(guò)程,一開(kāi)始多線(xiàn)程的優(yōu)勢(shì)就會(huì)非常明顯。
FillParallel方法,大家覺(jué)得有沒(méi)有其它問(wèn)題呢?想必一般人都能看出,這里有最初級(jí)的線(xiàn)程安全問(wèn)題。沒(méi)看出的應(yīng)該是剛學(xué).Net各種集合的初學(xué)者,線(xiàn)程安全對(duì)他們還只是個(gè)太虛幻境。不過(guò)借助這個(gè)Parallel,就可以輕松神游幻境。把FillParallel方法循環(huán)一百次執(zhí)行,會(huì)發(fā)現(xiàn)返回結(jié)果本來(lái)應(yīng)該有999個(gè)元素,輸出的卻顯示卻結(jié)果經(jīng)常少十幾二十個(gè)。如果創(chuàng)建List時(shí)賦的容量不夠,在List擴(kuò)容時(shí),還可能引發(fā)異常。一般是像下圖這樣(不過(guò)一百次都是999也不是不可能,要看你的RP了):
應(yīng)提醒一點(diǎn)的是,試驗(yàn)要在Release編譯模式下運(yùn)行,不然看不到線(xiàn)程安全問(wèn)題,并行執(zhí)行的效率提升得也很有限。我用的電腦都是雙核,不知道在單核電腦的運(yùn)行情況如何,可能有一定區(qū)別。
接著我改下邏輯,增加了一個(gè)是否Person存在重名的判斷,變成:
代碼 static IEnumerable<Person> testFillParallel(){
var list =new List<Person>(9);
Enumerable.Range(1, 999).AsParallel().ForAll(n =>
{
var name ="Person"+ n %9;
if (list.Count(p => p.Name == name) <1) list.Add(new Person { Id = n, Name = name });
});
Console.WriteLine("Person's count is {0}", list.Count);
return list;
}
RP不管用了,執(zhí)行幾次,必拋異常:System.InvalidOperationException: Collection was modified; enumeration operation may no execute.
一個(gè)線(xiàn)程在枚舉集合元素,這時(shí)必須保證集合不被其它線(xiàn)程修改,怎么辦呢?以前,就知道用鎖,現(xiàn)在據(jù)說(shuō)有了線(xiàn)程安全的集合類(lèi),在System.Collections.Concurrent命名空間下,有ConcurrentDictionary, ConcurrentQueue, ConcurrentStack,就是沒(méi)有ConcurrentList。費(fèi)了半天,才發(fā)現(xiàn)與List對(duì)應(yīng)的應(yīng)該是BlockingCollection。
把集合定義換成: var list = new BlockingCollection<Person>(9); 只見(jiàn)刷刷刷,哪怕執(zhí)行幾萬(wàn)次都可以一路跑完了。
不過(guò)這樣做,還是會(huì)發(fā)現(xiàn)問(wèn)題,不知大家看出了嗎?
總結(jié)
以上是生活随笔為你收集整理的.Net 4.0并行库实用性演练的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 智能化网络管理 为企业信息化保驾护航
- 下一篇: Windows2008+sqlserve