C#中的多線程小試牛刀
前言
昨天在上班時(shí)瀏覽博問(wèn),發(fā)現(xiàn)了一個(gè)問(wèn)題,雖然自己在 C# 多線程上沒(méi)有怎么嘗試過(guò),看了幾遍 CLR 中關(guān)于 線程的概念和講解(后面三章)。也想拿來(lái)實(shí)踐實(shí)踐。問(wèn)題定義是這樣的:
對(duì)于多線程不是很懂,面試的時(shí)候遇到一個(gè)多線程的題,不會(huì)做,分享出來(lái),懂的大佬指點(diǎn)一下,謝謝
建一個(gè)winform窗體,在窗體中放上一個(gè)開(kāi)始按鈕,一個(gè)停止按鈕,一個(gè)文本框,在窗體中聲明一個(gè)List類型的屬性,點(diǎn)擊開(kāi)始按鈕后開(kāi)啟10個(gè)線程,所有線程同時(shí)不間斷的給List集合中添加1-10000之間的隨機(jī)數(shù),要求添加List集合中的數(shù)字不能重復(fù),并且實(shí)時(shí)在文本框中顯示集合的長(zhǎng)度,當(dāng)集合List的長(zhǎng)度等于1000時(shí)自動(dòng)停止所有線程,如果中途點(diǎn)擊停止按鈕也停止所有線程,點(diǎn)擊開(kāi)始又繼續(xù)執(zhí)行。
我其實(shí)沒(méi)有完全實(shí)現(xiàn)了這位博問(wèn)中提問(wèn)的同學(xué)的需求,具體問(wèn)題的來(lái)源可查看該地址 問(wèn)題來(lái)源
開(kāi)始嘗試
剛拿到這個(gè)需求的時(shí)候,映入我腦海里的是 Task, Threadpool,Concurrent,和 Lock 等概念,接下來(lái)就是組裝和編碼的過(guò)程了,首先理一理頭緒,
- 生成隨機(jī)數(shù)
- 插入到 List 中,且不能重復(fù)
- 開(kāi)啟多個(gè)線程同時(shí)插入。
首先是生成 隨機(jī)數(shù),使用 System.Random 類來(lái)生成偽隨機(jī)數(shù)(這個(gè)其實(shí)性能和效率賊低,后面再敘述)
private int GenerateInt32Num()
{
var num = random.Next(0, TOTAL_NUM);
return num;
}
然后是插入到 List<Int32> 中的代碼,判斷是否 已經(jīng)達(dá)到了 我們需要的 List 長(zhǎng)度,如果已滿足,則退出程序。
private void AddToList(int num)
{
if (numList.Count == ENDNUM)
{
return;
}
numList.Add(num);
}
如果是個(gè) 單線程的,按照上面那樣 while(true) 然后一直插入即可,可這個(gè)是個(gè) 多線程,那么需要如何處理呢?
我思考了一下,想到了之前在 CLR 中學(xué)到的 可以用 CancellationTokenSource 中的 Cancel 來(lái)通知 Task 來(lái)取消操作。所以現(xiàn)在的邏輯是,用線程池來(lái)實(shí)現(xiàn)多線程。然后傳入 CancellationTokenSource.Token 來(lái)取消任務(wù)。
最后用 Task.WhanAny() 來(lái)獲取到第一個(gè)到達(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);
}
}
看起來(lái)是沒(méi)有什么問(wèn)題的,運(yùn)行了一下。得到了如下結(jié)果,
這應(yīng)該是昨晚運(yùn)行時(shí)得到的數(shù)據(jù),當(dāng)時(shí)也沒(méi)有多想,就貼了上去,回答了那位提問(wèn)同學(xué)的問(wèn)題。但是心里有一個(gè)疑惑,為什么會(huì)同時(shí)由 兩個(gè) Thread 同時(shí)達(dá)到了該目標(biāo)呢?
發(fā)現(xiàn)問(wèn)題
今天早上到公司時(shí),我又打開(kāi)了這個(gè) 代碼,發(fā)現(xiàn)確實(shí)有點(diǎn)不對(duì)勁,于是就和我邊上 做 Go 語(yǔ)言開(kāi)發(fā)的同學(xué),問(wèn)了問(wèn)他,哪里出現(xiàn)了問(wèn)題,他和我說(shuō):“你加了讀寫(xiě)鎖了嗎?” 你這里有數(shù)據(jù)臟讀寫(xiě)。心里面有了點(diǎn)眉目。
按照他說(shuō)的,修改了一下AddToList 里面的邏輯,這時(shí)候,確實(shí)解決了上面的問(wèn)題,
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
{
List<int> numList = new List<int>();
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);
}
}
}
這時(shí)候,那位 Go 語(yǔ)言的同學(xué)和我說(shuō),我們?cè)囋?1000w 的數(shù)據(jù)插入,看看需要多少時(shí)間?于是我讓他用 Go 語(yǔ)言實(shí)現(xiàn)了一下上面的邏輯,1000w數(shù)據(jù)用了 三分鐘,我讓他看看總共生成了多少隨機(jī)數(shù),他查看了一下生成了 1億4千多萬(wàn)的數(shù)據(jù)。
最開(kāi)始我用上面的代碼來(lái)測(cè),發(fā)現(xiàn)我插入 1000w 的數(shù)據(jù),CPU 到100% 而且花了挺長(zhǎng)時(shí)間,程序根本沒(méi)反應(yīng),查看了一下我判斷重復(fù)的語(yǔ)句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;
}
EqualityComparer<T> equalityComparer = EqualityComparer<T>.Default;
for (int index = 0; index < this._size; ++index)
{
if (equalityComparer.Equals(this._items[index], item))
return true;
}
return false;
}
可想而知,如果數(shù)據(jù)量很大的話,這個(gè)循環(huán)不就 及其緩慢嗎?
我于是請(qǐng)教了那位 GO 的同學(xué),判斷重復(fù)的邏輯用什么來(lái)實(shí)現(xiàn)的,他和我說(shuō)了一個(gè)位圖 bitmap 的概念,
我用其重寫(xiě)了一下判斷重復(fù)的邏輯,代碼如下:
int[] bitmap = new int[MAX_SIZE]; var index = num % TOTAL_NUM; bitMap[index] = 1; return bitMap[num] == 1;
在添加到 List 的時(shí)候,順便插入到 bitmap 中,判斷重復(fù)只需要根據(jù)當(dāng)前元素的位置是否 等于 1 即可,
我修改代碼后,跑了一下 1000w 的數(shù)據(jù)用來(lái) 3000+ ms。
這時(shí)候,引起了他的極度懷疑,一向以高性能并發(fā) 著稱的 Go 速度竟然這么慢嗎?他一度懷疑我的邏輯有問(wèn)題。
下午結(jié)束了一個(gè)階段的工作后,我又拾起了我上午寫(xiě)的代碼,果不其然,發(fā)現(xiàn)了邏輯錯(cuò)誤:
如下:
var insertNum = GenerateInt32Num();
if (numList.Contains(insertNum))
{
insertNum = GenerateInt32Num();
}
生成隨機(jī)數(shù)這里,這里有個(gè)大問(wèn)題,就是其實(shí)只判斷了一次,導(dǎo)致速度那么快,正確的寫(xiě)法應(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
{
List<int> numList = new List<int>();
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;
}
}
}
結(jié)果如下:
但是這個(gè)代碼執(zhí)行 1000w的數(shù)據(jù)需要好久。 這個(gè)問(wèn)題繼續(xù)研究。
源碼地址:https://github.com/doublnt/dotnetcore/tree/master/CSharpFundamental
總結(jié)
以上就是這篇文章的全部?jī)?nèi)容了,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,謝謝大家對(duì)我們的支持。
欄 目:C#教程
下一篇:C#多線程中的異常處理操作示例
本文標(biāo)題:C#中的多線程小試牛刀
本文地址:http://www.jygsgssxh.com/a1/C_jiaocheng/4763.html
您可能感興趣的文章
- 01-10C#停止線程的方法
- 01-10C#實(shí)現(xiàn)多線程下載文件的方法
- 01-10C#實(shí)現(xiàn)多線程寫(xiě)入同一個(gè)文件的方法
- 01-10C#獲取進(jìn)程或線程相關(guān)信息的方法
- 01-10C#通過(guò)Semaphore類控制線程隊(duì)列的方法
- 01-10C#線程隊(duì)列用法實(shí)例分析
- 01-10C#中查找Dictionary中的重復(fù)值的方法
- 01-10C#實(shí)現(xiàn)ComboBox控件顯示出多個(gè)數(shù)據(jù)源屬性的方法
- 01-10C#將圖片存放到SQL SERVER數(shù)據(jù)庫(kù)中的方法
- 01-10關(guān)于nancy中的身份驗(yàn)證


閱讀排行
- 1C語(yǔ)言 while語(yǔ)句的用法詳解
- 2java 實(shí)現(xiàn)簡(jiǎn)單圣誕樹(shù)的示例代碼(圣誕
- 3利用C語(yǔ)言實(shí)現(xiàn)“百馬百擔(dān)”問(wèn)題方法
- 4C語(yǔ)言中計(jì)算正弦的相關(guān)函數(shù)總結(jié)
- 5c語(yǔ)言計(jì)算三角形面積代碼
- 6什么是 WSH(腳本宿主)的詳細(xì)解釋
- 7C++ 中隨機(jī)函數(shù)random函數(shù)的使用方法
- 8正則表達(dá)式匹配各種特殊字符
- 9C語(yǔ)言十進(jìn)制轉(zhuǎn)二進(jìn)制代碼實(shí)例
- 10C語(yǔ)言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
本欄相關(guān)
- 01-10C#通過(guò)反射獲取當(dāng)前工程中所有窗體并
- 01-10關(guān)于ASP網(wǎng)頁(yè)無(wú)法打開(kāi)的解決方案
- 01-10WinForm限制窗體不能移到屏幕外的方法
- 01-10WinForm繪制圓角的方法
- 01-10C#實(shí)現(xiàn)txt定位指定行完整實(shí)例
- 01-10WinForm實(shí)現(xiàn)仿視頻播放器左下角滾動(dòng)新
- 01-10C#停止線程的方法
- 01-10C#實(shí)現(xiàn)清空回收站的方法
- 01-10C#通過(guò)重寫(xiě)Panel改變邊框顏色與寬度的
- 01-10C#實(shí)現(xiàn)讀取注冊(cè)表監(jiān)控當(dāng)前操作系統(tǒng)已
隨機(jī)閱讀
- 01-11Mac OSX 打開(kāi)原生自帶讀寫(xiě)NTFS功能(圖文
- 01-10C#中split用法實(shí)例總結(jié)
- 08-05織夢(mèng)dedecms什么時(shí)候用欄目交叉功能?
- 01-10SublimeText編譯C開(kāi)發(fā)環(huán)境設(shè)置
- 08-05DEDE織夢(mèng)data目錄下的sessions文件夾有什
- 04-02jquery與jsp,用jquery
- 01-10使用C語(yǔ)言求解撲克牌的順子及n個(gè)骰子
- 08-05dedecms(織夢(mèng))副欄目數(shù)量限制代碼修改
- 01-11ajax實(shí)現(xiàn)頁(yè)面的局部加載
- 01-10delphi制作wav文件的方法


