c# 并行和多線程編程——認(rèn)識(shí)Parallel
隨著多核時(shí)代的到來(lái),并行開(kāi)發(fā)越來(lái)越展示出它的強(qiáng)大威力!使用并行程序,充分的利用系統(tǒng)資源,提高程序的性能。在.net 4.0中,微軟給我們提供了一個(gè)新的命名空間:System.Threading.Tasks。這里面有很多關(guān)于并行開(kāi)發(fā)的東西,今天第一篇就介紹下最基礎(chǔ),最簡(jiǎn)單的——認(rèn)識(shí)和使用Parallel。
一、 Parallel的使用
在Parallel下面有三個(gè)常用的方法invoke,For和ForEach。
1、Parallel.Invoke
這是最簡(jiǎn)單,最簡(jiǎn)潔的將串行的代碼并行化。
在這里先講一個(gè)知識(shí)點(diǎn),就是StopWatch的使用,最近有一些人說(shuō)找不到StopWatch,StopWatch到底是什么東西,今天就來(lái)說(shuō)明一下。
StopWatch在System.Diagnostics命名控件,要使用它就要先引用這個(gè)命名空間。
其使用方法如下:
var stopWatch = new StopWatch(); //創(chuàng)建一個(gè)Stopwatch實(shí)例 stopWatch.Start(); //開(kāi)始計(jì)時(shí) stopWatch.Stop(); //停止計(jì)時(shí) stopWatch.Reset(); //重置StopWatch stopWatch.Restart(); //重新啟動(dòng)被停止的StopWatch stopWatch.ElapsedMilliseconds //獲取stopWatch從開(kāi)始到現(xiàn)在的時(shí)間差,單位是毫秒
本次用到的就這么多知識(shí)點(diǎn),想了解更多關(guān)于StopWatch的,去百度一下吧,網(wǎng)上有很多資料。
下面進(jìn)入整體,開(kāi)始介紹Parallel.Invoke方法,廢話不多說(shuō)了,首先新建一個(gè)控制臺(tái)程序,添加一個(gè)類,代碼如下:
public class ParallelDemo
{
private Stopwatch stopWatch = new Stopwatch();
public void Run1()
{
Thread.Sleep(2000);
Console.WriteLine("Task 1 is cost 2 sec");
}
public void Run2()
{
Thread.Sleep(3000);
Console.WriteLine("Task 2 is cost 3 sec");
}
public void ParallelInvokeMethod()
{
stopWatch.Start();
Parallel.Invoke(Run1, Run2);
stopWatch.Stop();
Console.WriteLine("Parallel run " + stopWatch.ElapsedMilliseconds + " ms.");
stopWatch.Restart();
Run1();
Run2();
stopWatch.Stop();
Console.WriteLine("Normal run " + stopWatch.ElapsedMilliseconds + " ms.");
}
}
代碼很簡(jiǎn)單,首先新加一個(gè)類,在類中寫(xiě)了兩個(gè)方法,Run1和Run2,分別等待一定時(shí)間,輸出一條信息,然后寫(xiě)了一個(gè)測(cè)試方法ParallelInvokeMethod,分別用兩種方法調(diào)用Run1和Run2,然后在main方法中調(diào)用,下面來(lái)看一下運(yùn)行時(shí)間如何:

大家應(yīng)該能夠猜到,正常調(diào)用的話應(yīng)該是5秒多,而Parallel.Invoke方法調(diào)用用了只有3秒,也就是耗時(shí)最長(zhǎng)的那個(gè)方法,可以看出方法是并行執(zhí)行的,執(zhí)行效率提高了很多。
2、Parallel.For
這個(gè)方法和For循環(huán)的功能相似,下面就在類中添加一個(gè)方法來(lái)測(cè)試一下吧。代碼如下:
public void ParallelForMethod()
{
stopWatch.Start();
for (int i = 0; i < 10000; i++)
{
for (int j = 0; j < 60000; j++)
{
int sum = 0;
sum += i;
}
}
stopWatch.Stop();
Console.WriteLine("NormalFor run " + stopWatch.ElapsedMilliseconds + " ms.");
stopWatch.Reset();
stopWatch.Start();
Parallel.For(0, 10000, item =>
{
for (int j = 0; j < 60000; j++)
{
int sum = 0;
sum += item;
}
});
stopWatch.Stop();
Console.WriteLine("ParallelFor run " + stopWatch.ElapsedMilliseconds + " ms.");
}
寫(xiě)了兩個(gè)循環(huán),做了一些沒(méi)有意義的事情,目的主要是為了消耗CPU時(shí)間,同理在main方法中調(diào)用,運(yùn)行結(jié)果如下圖:

可以看到,Parallel.For所用的時(shí)間比單純的for快了1秒多,可見(jiàn)提升的性能是非??捎^的。那么,是不是Parallel.For在任何時(shí)候都比f(wàn)or要快呢?答案當(dāng)然是“不是”,要不然微軟還留著for干嘛?
下面修改一下代碼,添加一個(gè)全局變量num,代碼如下:
public void ParallelForMethod()
{
var obj = new Object();
long num = 0;
ConcurrentBag<long> bag = new ConcurrentBag<long>();
stopWatch.Start();
for (int i = 0; i < 10000; i++)
{
for (int j = 0; j < 60000; j++)
{
//int sum = 0;
//sum += item;
num++;
}
}
stopWatch.Stop();
Console.WriteLine("NormalFor run " + stopWatch.ElapsedMilliseconds + " ms.");
stopWatch.Reset();
stopWatch.Start();
Parallel.For(0, 10000, item =>
{
for (int j = 0; j < 60000; j++)
{
//int sum = 0;
//sum += item;
lock (obj)
{
num++;
}
}
});
stopWatch.Stop();
Console.WriteLine("ParallelFor run " + stopWatch.ElapsedMilliseconds + " ms.");
}
Parallel.For由于是并行運(yùn)行的,所以會(huì)同時(shí)訪問(wèn)全局變量num,為了得到正確的結(jié)果,要使用lock,此時(shí)來(lái)看看運(yùn)行結(jié)果:

是不是大吃一驚???Parallel.For竟然用了15秒多,而for跟之前的差不多。這主要是由于并行同時(shí)訪問(wèn)全局變量,會(huì)出現(xiàn)資源爭(zhēng)奪,大多數(shù)時(shí)間消耗在了資源等待上面。
一直說(shuō)并行,那么從哪里可以看出來(lái)Parallel.For是并行執(zhí)行的呢?下面來(lái)寫(xiě)個(gè)測(cè)試代碼:
Parallel.For(0, 100, i =>
{
Console.Write(i + "\t");
});
從0輸出到99,運(yùn)行后會(huì)發(fā)現(xiàn)輸出的順序不對(duì),用for順序肯定是對(duì)的,并行同時(shí)執(zhí)行,所以會(huì)出現(xiàn)輸出順序不同的情況。
3、Parallel.Foreach
這個(gè)方法跟Foreach方法很相似,想具體了解的,可以百度些資料看看,這里就不多說(shuō)了,下面給出其使用方法:
List<int> list = new List<int>();
list.Add(0);
Parallel.ForEach(list, item =>
{
DoWork(item);
});
二、 Parallel中途退出循環(huán)和異常處理
1、當(dāng)我們使用到Parallel,必然是處理一些比較耗時(shí)的操作,當(dāng)然也很耗CPU和內(nèi)存,如果我們中途向停止,怎么辦呢?
在串行代碼中我們break一下就搞定了,但是并行就不是這么簡(jiǎn)單了,不過(guò)沒(méi)關(guān)系,在并行循環(huán)的委托參數(shù)中提供了一個(gè)ParallelLoopState,
該實(shí)例提供了Break和Stop方法來(lái)幫我們實(shí)現(xiàn)。
Break: 當(dāng)然這個(gè)是通知并行計(jì)算盡快的退出循環(huán),比如并行計(jì)算正在迭代100,那么break后程序還會(huì)迭代所有小于100的。
Stop:這個(gè)就不一樣了,比如正在迭代100突然遇到stop,那它啥也不管了,直接退出。
下面來(lái)寫(xiě)一段代碼測(cè)試一下:
public void ParallelBreak()
{
ConcurrentBag<int> bag = new ConcurrentBag<int>();
stopWatch.Start();
Parallel.For(0, 1000, (i, state) =>
{
if (bag.Count == 300)
{
state.Stop();
return;
}
bag.Add(i);
});
stopWatch.Stop();
Console.WriteLine("Bag count is " + bag.Count + ", " + stopWatch.ElapsedMilliseconds);
}
這里使用的是Stop,當(dāng)數(shù)量達(dá)到300個(gè)時(shí),會(huì)立刻停止;可以看到結(jié)果"Bag count is 300",如果用break,可能結(jié)果是300多個(gè)或者300個(gè),大家可以測(cè)試一下。
2、異常處理
首先任務(wù)是并行計(jì)算的,處理過(guò)程中可能會(huì)產(chǎn)生n多的異常,那么如何來(lái)獲取到這些異常呢?普通的Exception并不能獲取到異常,然而為并行誕生的AggregateExcepation就可以獲取到一組異常。
這里我們修改Parallel.Invoke的代碼,修改后代碼如下:
public class ParallelDemo
{
private Stopwatch stopWatch = new Stopwatch();
public void Run1()
{
Thread.Sleep(2000);
Console.WriteLine("Task 1 is cost 2 sec");
throw new Exception("Exception in task 1");
}
public void Run2()
{
Thread.Sleep(3000);
Console.WriteLine("Task 2 is cost 3 sec");
throw new Exception("Exception in task 2");
}
public void ParallelInvokeMethod()
{
stopWatch.Start();
try
{
Parallel.Invoke(Run1, Run2);
}
catch (AggregateException aex)
{
foreach (var ex in aex.InnerExceptions)
{
Console.WriteLine(ex.Message);
}
}
stopWatch.Stop();
Console.WriteLine("Parallel run " + stopWatch.ElapsedMilliseconds + " ms.");
stopWatch.Reset();
stopWatch.Start();
try
{
Run1();
Run2();
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
stopWatch.Stop();
Console.WriteLine("Normal run " + stopWatch.ElapsedMilliseconds + " ms.");
}
}
順序調(diào)用方法我把異常處理寫(xiě)一起了,這樣只能捕獲Run1的異常信息,大家可以分開(kāi)寫(xiě)。捕獲AggregateException 異常后,用foreach循環(huán)遍歷輸出異常信息,可以看到兩個(gè)異常信息都顯示了。
以上就是c# 并行和多線程編程——認(rèn)識(shí)Parallel的詳細(xì)內(nèi)容,更多關(guān)于c# 并行和多線程編程的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
C#12中的Primary?Constructors主構(gòu)造函數(shù)詳解
主構(gòu)造函數(shù)把參數(shù)添加到class與record的類聲明中就是主構(gòu)造函數(shù),這篇文章主要介紹了C#12中的Primary?Constructors 主構(gòu)造函數(shù),需要的朋友可以參考下2023-11-11
C#中DateTime的時(shí)間加減法操作小結(jié)
本文主要介紹了C#中DateTime的時(shí)間加減法操作小結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07
Unity編輯器資源導(dǎo)入處理函數(shù)OnPreprocessAudio用法示例
這篇文章主要為大家介紹了Unity編輯器資源導(dǎo)入處理函數(shù)OnPreprocessAudio用法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08
基于C#實(shí)現(xiàn)的多生產(chǎn)者多消費(fèi)者同步問(wèn)題實(shí)例
這篇文章主要介紹了基于C#實(shí)現(xiàn)的多生產(chǎn)者多消費(fèi)者同步問(wèn)題,包括了加鎖與釋放鎖,以及對(duì)應(yīng)臨界資源的訪問(wèn)。是比較實(shí)用的技巧,需要的朋友可以參考下2014-09-09
c#?如何將字符串轉(zhuǎn)換為大寫(xiě)或小寫(xiě)
這篇文章主要介紹了c#?如何將字符串轉(zhuǎn)換為大寫(xiě)或小寫(xiě),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-06-06
C#調(diào)用Python程序傳參數(shù)獲得返回值
C# 調(diào)用 Python 程序有多種方式,本文主要介紹了4種方式,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-02-02

