一個例子形象的理解協程和線程的區別

来源:https://www.cnblogs.com/s0611163/archive/2022/08/17/16593812.html
-Advertisement-
Play Games

一個例子形象的理解協程和線程的區別 Talk is cheap, show me the code! 所以,廢話先不說,先上代碼: 首先寫一個WebAPI介面 /// <summary> /// 測試介面 /// </summary> [RoutePrefix("api/test")] public ...


一個例子形象的理解協程和線程的區別

Talk is cheap, show me the code! 所以,廢話先不說,先上代碼:

首先寫一個WebAPI介面

/// <summary>
/// 測試介面
/// </summary>
[RoutePrefix("api/test")]
public class TestController : ApiController
{
    /// <summary>
    /// 測試GET請求
    /// </summary>
    /// <param name="val">測試參數</param>
    [HttpGet]
    [Route("TestGet")]
    public HttpResponseMessage TestGet(string val)
    {
        Thread.Sleep(200); //模擬執行耗時操作

        return new HttpResponseMessage { Content = new StringContent(val.ToString(), Encoding.UTF8, "text/plain") };
    }
}

測試代碼

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
using Utils;

namespace AsyncDemo2
{
    public partial class Form1 : Form
    {
        private int n = 200;

        public Form1()
        {
            InitializeComponent();

            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    Thread.Sleep(100);

                    ThreadPool.GetMaxThreads(out int w1, out int c1);
                    ThreadPool.GetAvailableThreads(out int w2, out int c2);

                    int w = w1 - w2;
                    int c = c1 - c2;
                    label1.BeginInvoke(new Action(() =>
                    {
                        label1.Text = string.Format("工作線程:{0} 非同步線程:{1}", w, c);
                    }));
                }
            }, TaskCreationOptions.LongRunning);
        }

        /// <summary>
        /// 日誌輸出
        /// </summary>
        private void Log(string msg)
        {
            this.BeginInvoke(new Action(() =>
            {
                textBox1.AppendText(DateTime.Now.ToString("mm:ss.fff") + ":" + msg + "\r\n");
            }));
        }

        /// <summary>
        /// 非同步請求
        /// </summary>
        private async Task ReqeustAsync(int val)
        {
            try
            {
                Log("非同步  開始請求" + val);
                string result = await HttpUtil.HttpGetAsync("http://localhost:8500/api/test/TestGet?val=" + val);
                Log("非同步  返回數據" + result + "  線程ID:" + Thread.CurrentThread.ManagedThreadId);
            }
            catch (Exception ex)
            {
                Log("出錯:" + ex.Message);
            }
        }

        /// <summary>
        /// 線上程中同步請求
        /// </summary>
        private Task Request(int val)
        {
            return Task.Run(() =>
            {
                try
                {
                    Log("同步多線程  開始請求" + val);
                    string result = HttpUtil.HttpGet("http://localhost:8500/api/test/TestGet?val=" + val);
                    Log("同步多線程  返回數據" + result + "  線程ID:" + Thread.CurrentThread.ManagedThreadId);
                }
                catch (Exception ex)
                {
                    Log("出錯:" + ex.Message);
                }
            });
        }

        //測試非同步請求
        private async void button3_Click(object sender, EventArgs e)
        {
            textBox1.Text = string.Empty;
            Stopwatch sw = new Stopwatch();
            List<Task> taskList = new List<Task>();
            sw.Start();

            for (int i = 0; i < n; i++)
            {
                Task t = ReqeustAsync(i);
                taskList.Add(t);
            }
            foreach (Task t in taskList)
            {
                await t;
            }

            Log(n + "個非同步請求完成,耗時:" + sw.Elapsed.TotalSeconds.ToString("0.000"));
            sw.Stop();
        }

        //測試多線程同步請求
        private void button4_Click(object sender, EventArgs e)
        {
            textBox1.Text = string.Empty;

            Task.Run(() =>
            {
                List<Task> taskList = new List<Task>();
                Stopwatch sw = new Stopwatch();
                sw.Start();

                for (int i = 0; i < n; i++)
                {
                    Task t = Request(i);
                    taskList.Add(t);
                }
                Task.WaitAll(taskList.ToArray());

                Log(n + "個多線程同步請求完成,耗時:" + sw.Elapsed.TotalSeconds.ToString("0.000"));
                sw.Stop();
            });
        }
    }
}

測試結果

測試結果
性能差9倍!

把WebAPI介面中模擬執行耗時操作改成1000毫秒再測試,測試結果如下:

測試結果
性能差10倍!

把Form1.cs構造函數中添加一行ThreadPool.SetMinThreads(20, 20);再測:
測試結果
設置線程池中線程的最小數量為20後,性能差距縮小了,性能只差4倍!為什麼?沒有設置線程池最小數量時,大約每1秒增加1到2個線程,線程增加速度太慢了,不影響協程性能,協程只需要很少的線程數量,但影響多線程性能。

把Form1.cs構造函數中代碼修改成ThreadPool.SetMinThreads(200, 200);再測:
測試結果
當線程池中線程數量足夠多時,性能差不多了!

結論

通過這個形象的例子,你體會到協程的好處了嗎?
有人可能會說,你怎麼不把WebAPI端改成非同步試試?WebAPI端是模擬的操作,在沒有外部操作(IO操作、資料庫操作等),僅有數據計算時,WebAPI端改成非同步沒區別。

有一個截圖中沒有體驗出來的,測試過程中,對於協程測試,工作線程和非同步線程始終為0,我想非同步線程應該是變化的,可能只是變化太快,看不出來。而多線程測試,測試過程中,我們可以看到工作線程的數量是大於0的,維持在一定數量,直到請求完成,也就是說,測試過程中,要占用一定數量的工作線程。

所以結論是什麼?
協程在執行耗時請求時,不會占用線程(註意占用這個詞,它肯定是使用線程的,但不會在耗時請求過程中占用),線上程池中線程數量較少時,協程的性能比多線程好很多。想一想,要是IO操作、資料庫操作,存在一些慢查詢、超時的,如果你使用多線程,你的線程池就爆了,協程就不會(Talk is cheap, show me the code!),後面附上測試。

WebAPI服務端補充說明

上面的測試,服務端我忘了說了,服務端啟動服務前,我加了一行代碼ThreadPool.SetMinThreads(200, 200);,因為你測試客戶端之前,服務端性能要跟上,不然測了個寂寞。
如果我把這行代碼刪掉,預熱後,再測:
測試結果
可以看到差距只有2.5倍了!因為服務端線程數量此時是1秒增加1、2個線程,服務端性能跟不上,客戶端的非同步請求自然也快不起來。

爆線程池測試

測試前修改:

  1. 把WebAPI介面中模擬執行耗時操作改成2000000毫秒,模擬服務端性能不行,反應慢。
  2. ThreadPool.SetMinThreads(20, 20);客戶端線程池最小線程數量設置為20,當然線程池越大越不容易爆,這裡為了更快重現出來,所以設置小一點,註意,我可沒有設置線程池上限!只是設置了下限。

測試視頻:
註意看測試時工作線程數量:
測試視頻
說明:協程,不論什麼時候點,都會有響應,當然可能後面點多了會報錯,但即使報錯,響應是有的。而多線程,後面點的,響應就很慢了。
測試視頻

你們可能會說你設置的線程池最小線程數量太小,改成ThreadPool.SetMinThreads(200, 200);,再測:

註意看工作線程數量!

WebAPI服務啟動代碼:

protected override void OnStart(string[] args)
{
    ThreadPool.SetMinThreads(200, 200);

    int port = int.Parse(ConfigurationManager.AppSettings["WebApiServicePort"]);
    StartOptions options = new StartOptions();
    options.Urls.Add("http://127.0.0.1:" + port);
    options.Urls.Add("http://localhost:" + port);
    options.Urls.Add("http://+:" + port);
    WebApp.Start<Startup>(options);
    LogUtil.Log("Web API 服務 啟動成功");
}

HttpUtil代碼:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Utils
{
    /// <summary>
    /// Http上傳下載文件
    /// </summary>
    public class HttpUtil
    {
        /// <summary>
        /// HttpGet
        /// </summary>
        /// <param name="url">url路徑名稱</param>
        /// <param name="cookie">cookie</param>
        public static async Task<string> HttpGetAsync(string url, CookieContainer cookie = null, WebHeaderCollection headers = null)
        {
            // 設置參數
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            request.CookieContainer = cookie;
            request.Method = "GET";
            request.ContentType = "text/plain;charset=utf-8";
            request.Timeout = Timeout.Infinite;

            if (headers != null)
            {
                foreach (string key in headers.Keys)
                {
                    request.Headers.Add(key, headers[key]);
                }
            }

            //發送請求並獲取相應回應數據
            HttpWebResponse response = (HttpWebResponse)(await request.GetResponseAsync());
            //直到request.GetResponse()程式才開始向目標網頁發送Post請求
            Stream instream = response.GetResponseStream();
            StreamReader sr = new StreamReader(instream, Encoding.UTF8);
            //返回結果網頁(html)代碼
            string content = await sr.ReadToEndAsync();
            instream.Close();
            return content;
        }

        /// <summary>
        /// HttpGet
        /// </summary>
        /// <param name="url">url路徑名稱</param>
        /// <param name="cookie">cookie</param>
        public static string HttpGet(string url, CookieContainer cookie = null, WebHeaderCollection headers = null)
        {
            // 設置參數
            HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
            request.CookieContainer = cookie;
            request.Method = "GET";
            request.ContentType = "text/plain;charset=utf-8";
            request.Timeout = Timeout.Infinite;

            if (headers != null)
            {
                foreach (string key in headers.Keys)
                {
                    request.Headers.Add(key, headers[key]);
                }
            }

            //發送請求並獲取相應回應數據
            HttpWebResponse response = (HttpWebResponse)request.GetResponse();
            //直到request.GetResponse()程式才開始向目標網頁發送Post請求
            Stream instream = response.GetResponseStream();
            StreamReader sr = new StreamReader(instream, Encoding.UTF8);
            //返回結果網頁(html)代碼
            string content = sr.ReadToEnd();
            instream.Close();
            return content;
        }
    }
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1、Jar 包 <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <maven.compiler.source>1.7</maven.compiler.source> <maven.comp ...
  • 1、conftest.py介紹 conftest.py是pytest框架的一種固定寫法,把fixture或者自己定義的插件寫到這個文件里就會自動去調用。我們前面都是將fixture寫到測試用例文件里,在實際工作中更推薦寫到conftest.py文件中,這樣更加靈活,易維護。 2、conftest.p ...
  • 1.樹的基礎知識概述 樹狀圖是一種數據結構,它是由 n(n>=1)個有限結點組成一個具有層次關係的集合。把它叫做“樹”是因為它看起來像一棵倒掛的樹,也就是說它是根朝上,而葉朝下的。它具有以下的特點:每個結點有零個或多個子結點;沒有父結點的結點稱為根結點;每一個非根結點有且只有一個父結點;除了根結點外 ...
  • “如果一個線程兩次調用start(),會出現什麼問題?” 如果這個問題出自阿裡p6崗位第一面的提問,你能回答出來嗎? 大家好,我是Mic,一個工作了14年的Java程式員。 關於這個問題,涉及到線程的生命周期,我把完整的回答整理到了15W字的面試文檔裡面大家可以私信我領取。 下麵來看看高手的回答。 ...
  • 作者:小牛呼嚕嚕 | https://xiaoniuhululu.com 電腦內功、JAVA底層、面試相關資料等更多精彩文章在公眾號「小牛呼嚕嚕 」 什麼是Java泛型 Java 泛型(generics)是 Jdk 5 中引入的一個新特性, 泛型提供了編譯時類型安全檢測機制, 該機制允許程式員在編 ...
  • JDK 9-17新功能30分鐘詳解-語法篇-var 介紹 JDK 10 JDK 10新增了新的關鍵字——var,官方文檔說作用是: Enhance the Java Language to extend type inference to declarations of local variable ...
  • Vue——​​兩分鐘概述 Vue 是一個JavaScript 框架。 在其最簡單的模式中,您可以簡單地將核心 Vue 腳本包含在您的應用程式中,然後開始構建您的組件。 除此之外,對於更複雜的應用程式,您可以使用 Vue 自己的 CLI 創建(並最終發佈)一個 Vue 項目。 與大多數其他 JavaS ...
  • 在現實生活中,生命周期一詞往往代表著某些人或事物從生到死的過程,而在依賴註入框架中,生命周期中的“生與死”體現為服務實例的創建和釋放。實際上對於介紹依賴註入框架的生命周期而言,就是在介紹依賴註入容器採用什麼樣的方式創建和釋放服務實例。 多個容器之間的組織結構 在介紹生命周期之前,我們必須先對“多個容 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...