這期內(nèi)容當(dāng)中小編將會給大家?guī)碛嘘P(guān)如何在C#項(xiàng)目中實(shí)現(xiàn)一個多線程,文章內(nèi)容豐富且以專業(yè)的角度為大家分析和敘述,閱讀完這篇文章希望大家可以有所收獲。
創(chuàng)新互聯(lián)主要從事網(wǎng)站設(shè)計、成都網(wǎng)站制作、網(wǎng)頁設(shè)計、企業(yè)做網(wǎng)站、公司建網(wǎng)站等業(yè)務(wù)。立足成都服務(wù)思禮,10年網(wǎng)站建設(shè)經(jīng)驗(yàn),價格優(yōu)惠、服務(wù)專業(yè),歡迎來電咨詢建站服務(wù):18982081108首先是生成 隨機(jī)數(shù),使用 System.Random 類來生成偽隨機(jī)數(shù)(這個其實(shí)性能和效率賊低,后面再敘述)
private int GenerateInt32Num() { var num = random.Next(0, TOTAL_NUM); return num; }
然后是插入到 List
private void AddToList(int num) { if (numList.Count == ENDNUM) { return; } numList.Add(num); }
如果是個 單線程的,按照上面那樣 while(true) 然后一直插入即可,可這個是個 多線程,那么需要如何處理呢?
我思考了一下,想到了之前在 CLR 中學(xué)到的 可以用 CancellationTokenSource 中的 Cancel 來通知 Task 來取消操作。所以現(xiàn)在的邏輯是,用線程池來實(shí)現(xiàn)多線程。然后傳入 CancellationTokenSource.Token 來取消任務(wù)。
最后用 Task.WhanAny() 來獲取到第一個到達(dá)此 Task 的 ID。
首先是建立 Task[] 的數(shù)組
internal void DoTheCompeteSecond() { Task[] tasks = new Task[10]; for (int i = 0; i < 10; ++i) { int num = i; tasks[i] = Task.Factory.StartNew(() => AddNumToList(num, cts), cts.Token); } Task.WaitAny(tasks); }
然后 AddNumToList 方法是這樣定義的,
private void AddNumToList(object state, CancellationTokenSource cts) {- Console.WriteLine("This is the {0} thread,Current ThreadId={1}", state, Thread.CurrentThread.ManagedThreadId); while (!cts.Token.IsCancellationRequested) { if (GetTheListCount() == ENDNUM) { cts.Cancel(); Console.WriteLine("Current Thread Id={0},Current Count={1}", Thread.CurrentThread.ManagedThreadId, GetTheListCount()); break; } var insertNum = GenerateInt32Num(); if (numList.Contains(insertNum)) { insertNum = GenerateInt32Num(); } AddToList(insertNum); } }
看起來是沒有什么問題的,運(yùn)行了一下。得到了如下結(jié)果,
這應(yīng)該是昨晚運(yùn)行時得到的數(shù)據(jù),當(dāng)時也沒有多想,就貼了上去,回答了那位提問同學(xué)的問題。但是心里有一個疑惑,為什么會同時由 兩個 Thread 同時達(dá)到了該目標(biāo)呢?
發(fā)現(xiàn)問題
今天早上到公司時,我又打開了這個 代碼,發(fā)現(xiàn)確實(shí)有點(diǎn)不對勁,于是就和我邊上 做 Go 語言開發(fā)的同學(xué),問了問他,哪里出現(xiàn)了問題,他和我說:“你加了讀寫鎖了嗎?” 你這里有數(shù)據(jù)臟讀寫。心里面有了點(diǎn)眉目。
按照他說的,修改了一下AddToList 里面的邏輯,這時候,確實(shí)解決了上面的問題,
private void AddToList(int num) { rwls.EnterReadLock(); if (numList.Count == ENDNUM) return; rwls.ExitReadLock(); rwls.EnterWriteLock(); numList.Add(num); rwls.ExitWriteLock(); }
得到的結(jié)果如下:
完整的代碼如下所示:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace CSharpFundamental { class MultipleThreadCompete { ListnumList = new List (); Random random = new Random(); CancellationTokenSource cts = new CancellationTokenSource(); private const int ENDNUM = 1000000; ReaderWriterLockSlim rwls = new ReaderWriterLockSlim(); internal void DoTheCompeteSecond() { Stopwatch sw = new Stopwatch(); sw.Start(); Task[] tasks = new Task[100]; for (int i = 0; i < 100; ++i) { int num = i; tasks[i] = Task.Run(() => AddNumToList(num, cts), cts.Token); } Task.WaitAny(tasks); Console.WriteLine("ExecuteTime={0}", sw.ElapsedMilliseconds / 1000); } private int GetTheListCount() { return numList.Count; } private void AddToList(int num) { rwls.EnterReadLock(); if (numList.Count == ENDNUM) return; rwls.ExitReadLock(); rwls.EnterWriteLock(); numList.Add(num); rwls.ExitWriteLock(); } private void AddNumToList(object state, CancellationTokenSource cts) { Console.WriteLine("This is the {0} thread,Current ThreadId={1}", state, Thread.CurrentThread.ManagedThreadId); while (!cts.Token.IsCancellationRequested) { try { rwls.EnterReadLock(); if (numList.Count == ENDNUM) { cts.Cancel(); Console.WriteLine("Current Thread Id={0},Current Count={1}", Thread.CurrentThread.ManagedThreadId, GetTheListCount()); break; } } finally { rwls.ExitReadLock(); } var insertNum = GenerateInt32Num(); if (numList.Contains(insertNum)) { insertNum = GenerateInt32Num(); } AddToList(insertNum); } } private int GenerateInt32Num() { return random.Next(1, ENDNUM); } } }
這時候,那位 Go 語言的同學(xué)和我說,我們試試 1000w 的數(shù)據(jù)插入,看看需要多少時間?于是我讓他用 Go 語言實(shí)現(xiàn)了一下上面的邏輯,1000w數(shù)據(jù)用了 三分鐘,我讓他看看總共生成了多少隨機(jī)數(shù),他查看了一下生成了 1億4千多萬的數(shù)據(jù)。
最開始我用上面的代碼來測,發(fā)現(xiàn)我插入 1000w 的數(shù)據(jù),CPU 到100% 而且花了挺長時間,程序根本沒反應(yīng),查看了一下我判斷重復(fù)的語句numList.Contains()
底層實(shí)現(xiàn)的代碼為:
[__DynamicallyInvokable] public bool Contains(T item) { if ((object) item == null) { for (int index = 0; index < this._size; ++index) { if ((object) this._items[index] == null) return true; } return false; } EqualityComparerequalityComparer = EqualityComparer .Default; for (int index = 0; index < this._size; ++index) { if (equalityComparer.Equals(this._items[index], item)) return true; } return false; }
可想而知,如果數(shù)據(jù)量很大的話,這個循環(huán)不就 及其緩慢嗎?
我于是請教了那位 GO 的同學(xué),判斷重復(fù)的邏輯用什么來實(shí)現(xiàn)的,他和我說了一個位圖 bitmap 的概念,
我用其重寫了一下判斷重復(fù)的邏輯,代碼如下:
int[] bitmap = new int[MAX_SIZE]; var index = num % TOTAL_NUM; bitMap[index] = 1; return bitMap[num] == 1;
在添加到 List 的時候,順便插入到 bitmap 中,判斷重復(fù)只需要根據(jù)當(dāng)前元素的位置是否 等于 1 即可,
我修改代碼后,跑了一下 1000w 的數(shù)據(jù)用來 3000+ ms。
這時候,引起了他的極度懷疑,一向以高性能并發(fā) 著稱的 Go 速度竟然這么慢嗎?他一度懷疑我的邏輯有問題。
下午結(jié)束了一個階段的工作后,我又拾起了我上午寫的代碼,果不其然,發(fā)現(xiàn)了邏輯錯誤:
如下:
var insertNum = GenerateInt32Num(); if (numList.Contains(insertNum)) { insertNum = GenerateInt32Num(); }
生成隨機(jī)數(shù)這里,這里有個大問題,就是其實(shí)只判斷了一次,導(dǎo)致速度那么快,正確的寫法應(yīng)該是
while (ContainsNum(currentNum)) { currentNum = GenerateInt32Num(); } private int GenerateInt32Num() { var num = random.Next(0, TOTAL_NUM); //Console.WriteLine(num); return num; }
最后的代碼如下:
using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; using System.Threading.Tasks; namespace CSharpFundamental { class MultipleThreadCompete { ListnumList = new List (); Random random = new Random(); CancellationTokenSource cts = new CancellationTokenSource(); private const int TOTAL_NUM = 1000000; private const int CURRENT_THREAD_COUNT = 35; ReaderWriterLockSlim rwls = new ReaderWriterLockSlim(); int[] bitMap = new int[TOTAL_NUM]; internal void DoTheCompete() { //ThreadPool.SetMinThreads(CURRENT_THREAD_COUNT, CURRENT_THREAD_COUNT); Stopwatch sw = new Stopwatch(); sw.Start(); Task[] tasks = new Task[CURRENT_THREAD_COUNT]; for (int i = 0; i < CURRENT_THREAD_COUNT; ++i) { int num = i; tasks[i] = Task.Run(() => ExecuteTheTask(num, cts), cts.Token); } Task.WaitAny(tasks); Console.WriteLine("ExecuteTime={0}", sw.ElapsedMilliseconds); } private int GetTheListCount() { return numList.Count; } private void AddToList(int num) { if (numList.Count == TOTAL_NUM) return; numList.Add(num); var index = num % TOTAL_NUM; bitMap[index] = 1; } private void ExecuteTheTask(object state, CancellationTokenSource cts) { Console.WriteLine("This is the {0} thread,Current ThreadId={1}", state, Thread.CurrentThread.ManagedThreadId); while (!cts.Token.IsCancellationRequested) { try { rwls.EnterReadLock(); if (numList.Count == TOTAL_NUM) { cts.Cancel(); Console.WriteLine("Current Thread Id={0},Current Count={1}", Thread.CurrentThread.ManagedThreadId, GetTheListCount()); break; } } finally { rwls.ExitReadLock(); } var currentNum = GenerateInt32Num(); while (ContainsNum(currentNum)) { currentNum = GenerateInt32Num(); } rwls.EnterWriteLock(); AddToList(currentNum); rwls.ExitWriteLock(); } } private int GenerateInt32Num() { var num = random.Next(0, TOTAL_NUM); //Console.WriteLine(num); return num; } private bool ContainsNum(int num) { rwls.EnterReadLock(); var contains = bitMap[num] == 1; rwls.ExitReadLock(); return contains; } } }
上述就是小編為大家分享的如何在C#項(xiàng)目中實(shí)現(xiàn)一個多線程了,如果剛好有類似的疑惑,不妨參照上述分析進(jìn)行理解。如果想知道更多相關(guān)知識,歡迎關(guān)注創(chuàng)新互聯(lián)成都網(wǎng)站設(shè)計公司行業(yè)資訊頻道。
另外有需要云服務(wù)器可以了解下創(chuàng)新互聯(lián)scvps.cn,海內(nèi)外云服務(wù)器15元起步,三天無理由+7*72小時售后在線,公司持有idc許可證,提供“云服務(wù)器、裸金屬服務(wù)器、高防服務(wù)器、香港服務(wù)器、美國服務(wù)器、虛擬主機(jī)、免備案服務(wù)器”等云主機(jī)租用服務(wù)以及企業(yè)上云的綜合解決方案,具有“安全穩(wěn)定、簡單易用、服務(wù)可用性高、性價比高”等特點(diǎn)與優(yōu)勢,專為企業(yè)上云打造定制,能夠滿足用戶豐富、多元化的應(yīng)用場景需求。