欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

C#中的多線程小試牛刀

 更新時(shí)間:2019年05月24日 10:55:02   作者:BUTTERAPPLE  
這篇文章主要給大家介紹了關(guān)于C#中多線程的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用C#具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

昨天在上班時(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è)開始按鈕,一個(gè)停止按鈕,一個(gè)文本框,在窗體中聲明一個(gè)List類型的屬性,點(diǎn)擊開始按鈕后開啟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)擊開始又繼續(xù)執(zhí)行。

我其實(shí)沒(méi)有完全實(shí)現(xiàn)了這位博問(wèn)中提問(wèn)的同學(xué)的需求,具體問(wèn)題的來(lái)源可查看該地址 問(wèn)題來(lái)源

開始嘗試

剛拿到這個(gè)需求的時(shí)候,映入我腦海里的是 Task, Threadpool,Concurrent,和 Lock 等概念,接下來(lái)就是組裝和編碼的過(guò)程了,首先理一理頭緒,

  • 生成隨機(jī)數(shù)
  • 插入到 List 中,且不能重復(fù)
  • 開啟多個(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í),我又打開了這個(gè) 代碼,發(fā)現(xiàn)確實(shí)有點(diǎn)不對(duì)勁,于是就和我邊上 做 Go 語(yǔ)言開發(fā)的同學(xué),問(wèn)了問(wèn)他,哪里出現(xiàn)了問(wèn)題,他和我說(shuō):“你加了讀寫鎖了嗎?” 你這里有數(shù)據(jù)臟讀寫。心里面有了點(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ù)。

最開始我用上面的代碼來(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 的概念,

我用其重寫了一下判斷重復(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è)階段的工作后,我又拾起了我上午寫的代碼,果不其然,發(fā)現(xiàn)了邏輯錯(cuò)誤:

如下:

var insertNum = GenerateInt32Num();
if (numList.Contains(insertNum))
{
 insertNum = GenerateInt32Num();
}

生成隨機(jī)數(shù)這里,這里有個(gè)大問(wèn)題,就是其實(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
 {
  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ì)腳本之家的支持。

相關(guān)文章

  • C#中接口(Interface)的深入詳解

    C#中接口(Interface)的深入詳解

    這篇文章主要給大家介紹了關(guān)于C#中接口(Interface)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • C#多線程系列之a(chǎn)sync和await用法詳解

    C#多線程系列之a(chǎn)sync和await用法詳解

    本文詳細(xì)講解了C#多線程中async和await的用法,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-02-02
  • C#中Try-Catch語(yǔ)句真的影響程序性能嗎?

    C#中Try-Catch語(yǔ)句真的影響程序性能嗎?

    這篇文章主要介紹了C#中Try-Catch語(yǔ)句真的影響程序性能嗎?本文結(jié)合IL分析Try-Catch語(yǔ)句的性能問(wèn)題,需要的朋友可以參考下
    2015-06-06
  • Unity 修改FBX模型動(dòng)畫的操作

    Unity 修改FBX模型動(dòng)畫的操作

    這篇文章主要介紹了Unity 修改FBX模型動(dòng)畫的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2021-04-04
  • C# mysql 插入數(shù)據(jù),中文亂碼的解決方法

    C# mysql 插入數(shù)據(jù),中文亂碼的解決方法

    用C#操作mysql時(shí), 插入數(shù)據(jù)中文都是亂碼,只顯示問(wèn)號(hào),數(shù)據(jù)庫(kù)本身使用的是utf-8字符
    2013-10-10
  • 帶你復(fù)習(xí)c# 托管和非托管資源

    帶你復(fù)習(xí)c# 托管和非托管資源

    這篇文章主要介紹了c# 托管和非托管資源的相關(guān)資料,文中講解非常細(xì)致,幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • c# 基于wpf,開發(fā)OFD電子文檔閱讀器

    c# 基于wpf,開發(fā)OFD電子文檔閱讀器

    這篇文章主要介紹了c# 如何基于wpf,開發(fā)OFD電子文檔閱讀器,幫助大家更好的理解和學(xué)習(xí)使用c#的wpf技術(shù),感興趣的朋友可以了解下
    2021-03-03
  • mvc開啟gzip壓縮示例分享

    mvc開啟gzip壓縮示例分享

    這篇文章主要介紹了mvc開啟gzip壓縮示例,需要的朋友可以參考下
    2014-03-03
  • C#通過(guò)標(biāo)簽軟件Bartender的ZPL命令打印條碼

    C#通過(guò)標(biāo)簽軟件Bartender的ZPL命令打印條碼

    這篇文章介紹了C#通過(guò)標(biāo)簽軟件Bartender的ZPL命令打印條碼,對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-01-01
  • C#構(gòu)造函數(shù)詳解

    C#構(gòu)造函數(shù)詳解

    本文詳細(xì)講解了C#中的構(gòu)造函數(shù),文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-04-04

最新評(píng)論