一、簡介sql
再說Windows的異步I/O操做前,先聊聊一些題外話,能幫助咱們更好的理解異步I/O操做,常規的Web程序,當用戶發起一次請求,當請求經過管道到達客戶端的這個過程,會喚起一個線程池線程(後臺線程),處理咱們的業務代碼,即全部的用戶請求是經過異步的方式發起的,這個過程,.Net Framework會自動進行,即便咱們沒有顯示的經過代碼來實現這個過程.因此這個過程明顯是存在性能瓶頸的,假設如今有一個4核服務器,意味這該服務器同時只能處理4個用戶請求(超理想狀況下,通常不可能),可是這個時候來了10000個用戶請求(併發執行)的狀況下,那麼意味者大量線程會堆積起來,等待着前面的線程執行完畢,同時進行頻繁的上下文切換,這個時候你會發現CPU會爆表.數據庫
上面只是一個例子,再說一個數據庫的例子,如今須要向數據庫插入20000條記錄,分爲三個版本去實現,第一個版本是單個線程同步插入,第二個版本多線程同步插入(Parallel),第三個版本多線程異步插入,來比較下性能和CPU利用零及使用狀況.api
(1)、單線程同步版本服務器
這個場景是隻有一個用戶請求進來,進行20000次的數據庫插入操做,這個版本不會產生線程堆積,由於全部的插入操做都只由主線程完成.多線程
private static readonly string ConnectionStrings; static Program() { //配置數據庫鏈接 ConnectionStrings = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; } static void Main(string[] args) { var stop = Stopwatch.StartNew(); InstertSync(); stop.Stop(); Console.WriteLine($"同步執行20000次插入操做,耗時:{stop.ElapsedMilliseconds/1000}秒"); Console.ReadKey(); } private static void InstertSync() { var totalCount = 0; var failCount = 0; //這裏以同步方式執行數據庫操做,注這裏只有一個線程執行全部的數據庫插入操做 for (int i = 0; i <= 20000; i++) { var conn = new SqlConnection(ConnectionStrings); conn.Open(); try { //模擬數據庫耗時操做 var sql = "insert into [dbo].[User]([Amount]) values (@Amount)"; var command = new SqlCommand(sql, conn); command.Parameters.Add( new SqlParameter("@Amount", i) ); //這裏線程會等待這一段時間,等待數據庫返回結果,並繼續執行下面的代碼 var result = command.ExecuteNonQuery(); if (result == 1) { totalCount+=1; } else { failCount+=1; } Console.WriteLine($"成功插入{totalCount}條記錄,插入失敗{failCount}條記錄"); } catch (Exception ex) { throw ex; } finally { conn.Close(); conn.Dispose(); } } }
再看看數據庫的批請求數數據併發
大概穩定在300次左右每秒異步
(2)、多線程同步async
這個場景是大多數沒有使用Async Await模型的Web應用程序(Parallel表明同時有多個用戶請求進來),同時數據庫也使用的是同步Api,這個時候以同步的方式發起數據庫請求,每一個線程會等待不肯定的時間,等待數據庫返回結果,同時另外一個線程開啓,也會等待數據庫返回結果,這樣用戶請求一多,就會產生大量的線程堆積,形成大量的內存浪費,並且當數據庫開始響應線程時,線程會被喚醒,所有開始執行,這時候CPU又會開始繁忙的執行.性能
private static readonly string ConnectionStrings; static Program() { //配置數據庫鏈接 ConnectionStrings = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; } static void Main(string[] args) { InsertAsync(); Console.ReadKey(); } private static void InsertAsync() { var stop = Stopwatch.StartNew(); var totalCount = 0; var failCount = 0; var res = Parallel.For(0, 20000, i => { var conn = new SqlConnection(ConnectionStrings); conn.Open(); try { //模擬數據庫耗時操做 var sql = "insert into [dbo].[User]([Amount]) values (@Amount)"; var command = new SqlCommand(sql, conn); command.Parameters.Add( new SqlParameter("@Amount", i) ); var result = command.ExecuteNonQuery(); if (result == 1) { Interlocked.Add(ref totalCount, 1); } else { Interlocked.Add(ref failCount, 1); } Console.WriteLine($"成功插入{totalCount}條記錄,插入失敗{failCount}條記錄"); } catch (Exception ex) { throw ex; } finally { conn.Close(); conn.Dispose(); } }); if (res.IsCompleted) { stop.Stop(); Console.WriteLine($"同步執行20000次插入操做,耗時:{stop.ElapsedMilliseconds / 1000}秒"); } }
去除Interlocked稍稍快一些.明顯能夠發如今多線程環境下,使用同步的數據庫操做api,效率顯著降低.CPU的利用率也很低,同時跑了不少操做線程,但數據庫使用同步Api,只能響應一個線程,其他的都須要排隊.spa
再看看數據庫批請求數
只能穩定在130次左右,說明多線程環境下,使用同步數據庫操做,阻礙了請求的提交速度.我的理解.
(3)、多線程異步
這個場景用戶使用基於Async Await模型的Web程序,且使用數據庫的異步Api
private static readonly string ConnectionStrings; static Program() { //配置數據庫鏈接 ConnectionStrings = ConfigurationManager.ConnectionStrings["connStr"].ConnectionString; } static void Main(string[] args) { InsertAsync(); Console.ReadKey(); } private static void InsertAsync() { var stop = Stopwatch.StartNew(); var totalCount = 0; var failCount = 0; var res = Parallel.For(0, 20000,async i => { var conn = new SqlConnection(ConnectionStrings); conn.Open(); try { //模擬數據庫耗時操做 var sql = "insert into [dbo].[User]([Amount]) values (@Amount)"; var command = new SqlCommand(sql, conn); command.Parameters.Add( new SqlParameter("@Amount", i) ); var result =await command.ExecuteNonQueryAsync(); if (result == 1) { Interlocked.Add(ref totalCount, 1); } else { Interlocked.Add(ref failCount, 1); } Console.WriteLine($"成功插入"); } catch (Exception ex) { throw ex; } finally { conn.Close(); conn.Dispose(); } }); if (res.IsCompleted) { stop.Stop(); Console.WriteLine($"同步執行20000次插入操做,耗時:{stop.ElapsedMilliseconds / 1000}秒"); } }
能夠發現這個模式插入效率很是之高.可是它的插入是無序的,由於Parallel執行線程的順序是無序的.CPU的利用率也是極高的.
再看看數據庫批請求數
直線飆升>1000次的請求提交,說明使用異步Api數據庫每秒接收的請求數,遠大於同步方式,也是使用異步Api如此之快的緣由.