ToB企服应用市场:ToB评测及商务社交产业平台

标题: 一个例子形象的理解协程和线程的区别 [打印本页]

作者: 大连全瓷种植牙齿制作中心    时间: 2022-9-16 17:17
标题: 一个例子形象的理解协程和线程的区别
一个例子形象的理解协程和线程的区别

Talk is cheap, show me the code! 所以,废话先不说,先上代码:
首先写一个WebAPI接口
  1. /// <summary>
  2. /// 测试接口
  3. /// </summary>
  4. [RoutePrefix("api/test")]
  5. public class TestController : ApiController
  6. {
  7.     /// <summary>
  8.     /// 测试GET请求
  9.     /// </summary>
  10.     /// <param name="val">测试参数</param>
  11.     [HttpGet]
  12.     [Route("TestGet")]
  13.     public HttpResponseMessage TestGet(string val)
  14.     {
  15.         Thread.Sleep(200); //模拟执行耗时操作
  16.         return new HttpResponseMessage { Content = new StringContent(val.ToString(), Encoding.UTF8, "text/plain") };
  17.     }
  18. }
复制代码
测试代码
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Threading.Tasks;
  8. using System.Windows.Forms;
  9. using Utils;
  10. namespace AsyncDemo2
  11. {
  12.     public partial class Form1 : Form
  13.     {
  14.         private int n = 200;
  15.         public Form1()
  16.         {
  17.             InitializeComponent();
  18.             Task.Factory.StartNew(() =>
  19.             {
  20.                 while (true)
  21.                 {
  22.                     Thread.Sleep(100);
  23.                     ThreadPool.GetMaxThreads(out int w1, out int c1);
  24.                     ThreadPool.GetAvailableThreads(out int w2, out int c2);
  25.                     int w = w1 - w2;
  26.                     int c = c1 - c2;
  27.                     label1.BeginInvoke(new Action(() =>
  28.                     {
  29.                         label1.Text = string.Format("工作线程:{0} 异步线程:{1}", w, c);
  30.                     }));
  31.                 }
  32.             }, TaskCreationOptions.LongRunning);
  33.         }
  34.         /// <summary>
  35.         /// 日志输出
  36.         /// </summary>
  37.         private void Log(string msg)
  38.         {
  39.             this.BeginInvoke(new Action(() =>
  40.             {
  41.                 textBox1.AppendText(DateTime.Now.ToString("mm:ss.fff") + ":" + msg + "\r\n");
  42.             }));
  43.         }
  44.         /// <summary>
  45.         /// 异步请求
  46.         /// </summary>
  47.         private async Task ReqeustAsync(int val)
  48.         {
  49.             try
  50.             {
  51.                 Log("异步  开始请求" + val);
  52.                 string result = await HttpUtil.HttpGetAsync("http://localhost:8500/api/test/TestGet?val=" + val);
  53.                 Log("异步  返回数据" + result + "  线程ID:" + Thread.CurrentThread.ManagedThreadId);
  54.             }
  55.             catch (Exception ex)
  56.             {
  57.                 Log("出错:" + ex.Message);
  58.             }
  59.         }
  60.         /// <summary>
  61.         /// 在线程中同步请求
  62.         /// </summary>
  63.         private Task Request(int val)
  64.         {
  65.             return Task.Run(() =>
  66.             {
  67.                 try
  68.                 {
  69.                     Log("同步多线程  开始请求" + val);
  70.                     string result = HttpUtil.HttpGet("http://localhost:8500/api/test/TestGet?val=" + val);
  71.                     Log("同步多线程  返回数据" + result + "  线程ID:" + Thread.CurrentThread.ManagedThreadId);
  72.                 }
  73.                 catch (Exception ex)
  74.                 {
  75.                     Log("出错:" + ex.Message);
  76.                 }
  77.             });
  78.         }
  79.         //测试异步请求
  80.         private async void button3_Click(object sender, EventArgs e)
  81.         {
  82.             textBox1.Text = string.Empty;
  83.             Stopwatch sw = new Stopwatch();
  84.             List<Task> taskList = new List<Task>();
  85.             sw.Start();
  86.             for (int i = 0; i < n; i++)
  87.             {
  88.                 Task t = ReqeustAsync(i);
  89.                 taskList.Add(t);
  90.             }
  91.             foreach (Task t in taskList)
  92.             {
  93.                 await t;
  94.             }
  95.             Log(n + "个异步请求完成,耗时:" + sw.Elapsed.TotalSeconds.ToString("0.000"));
  96.             sw.Stop();
  97.         }
  98.         //测试多线程同步请求
  99.         private void button4_Click(object sender, EventArgs e)
  100.         {
  101.             textBox1.Text = string.Empty;
  102.             Task.Run(() =>
  103.             {
  104.                 List<Task> taskList = new List<Task>();
  105.                 Stopwatch sw = new Stopwatch();
  106.                 sw.Start();
  107.                 for (int i = 0; i < n; i++)
  108.                 {
  109.                     Task t = Request(i);
  110.                     taskList.Add(t);
  111.                 }
  112.                 Task.WaitAll(taskList.ToArray());
  113.                 Log(n + "个多线程同步请求完成,耗时:" + sw.Elapsed.TotalSeconds.ToString("0.000"));
  114.                 sw.Stop();
  115.             });
  116.         }
  117.     }
  118. }
复制代码
测试结果


性能差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个线程,服务端性能跟不上,客户端的异步请求自然也快不起来。
爆线程池测试

测试前修改:
测试视频:
注意看测试时工作线程数量:

说明:协程,不论什么时候点,都会有响应,当然可能后面点多了会报错,但即使报错,响应是有的。而多线程,后面点的,响应就很慢了。

你们可能会说你设置的线程池最小线程数量太小,改成ThreadPool.SetMinThreads(200, 200);,再测:

注意看工作线程数量!


WebAPI服务启动代码:
  1. protected override void OnStart(string[] args)
  2. {
  3.     ThreadPool.SetMinThreads(200, 200);
  4.     int port = int.Parse(ConfigurationManager.AppSettings["WebApiServicePort"]);
  5.     StartOptions options = new StartOptions();
  6.     options.Urls.Add("http://127.0.0.1:" + port);
  7.     options.Urls.Add("http://localhost:" + port);
  8.     options.Urls.Add("http://+:" + port);
  9.     WebApp.Start<Startup>(options);
  10.     LogUtil.Log("Web API 服务 启动成功");
  11. }
复制代码
HttpUtil代码:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Net;
  6. using System.Text;
  7. using System.Threading;
  8. using System.Threading.Tasks;
  9. namespace Utils
  10. {
  11.     /// <summary>
  12.     /// Http上传下载文件
  13.     /// </summary>
  14.     public class HttpUtil
  15.     {
  16.         /// <summary>
  17.         /// HttpGet
  18.         /// </summary>
  19.         /// <param name="url">url路径名称</param>
  20.         /// <param name="cookie">cookie</param>
  21.         public static async Task<string> HttpGetAsync(string url, CookieContainer cookie = null, WebHeaderCollection headers = null)
  22.         {
  23.             // 设置参数
  24.             HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
  25.             request.CookieContainer = cookie;
  26.             request.Method = "GET";
  27.             request.ContentType = "text/plain;charset=utf-8";
  28.             request.Timeout = Timeout.Infinite;
  29.             if (headers != null)
  30.             {
  31.                 foreach (string key in headers.Keys)
  32.                 {
  33.                     request.Headers.Add(key, headers[key]);
  34.                 }
  35.             }
  36.             //发送请求并获取相应回应数据
  37.             HttpWebResponse response = (HttpWebResponse)(await request.GetResponseAsync());
  38.             //直到request.GetResponse()程序才开始向目标网页发送Post请求
  39.             Stream instream = response.GetResponseStream();
  40.             StreamReader sr = new StreamReader(instream, Encoding.UTF8);
  41.             //返回结果网页(html)代码
  42.             string content = await sr.ReadToEndAsync();
  43.             instream.Close();
  44.             return content;
  45.         }
  46.         /// <summary>
  47.         /// HttpGet
  48.         /// </summary>
  49.         /// <param name="url">url路径名称</param>
  50.         /// <param name="cookie">cookie</param>
  51.         public static string HttpGet(string url, CookieContainer cookie = null, WebHeaderCollection headers = null)
  52.         {
  53.             // 设置参数
  54.             HttpWebRequest request = WebRequest.Create(url) as HttpWebRequest;
  55.             request.CookieContainer = cookie;
  56.             request.Method = "GET";
  57.             request.ContentType = "text/plain;charset=utf-8";
  58.             request.Timeout = Timeout.Infinite;
  59.             if (headers != null)
  60.             {
  61.                 foreach (string key in headers.Keys)
  62.                 {
  63.                     request.Headers.Add(key, headers[key]);
  64.                 }
  65.             }
  66.             //发送请求并获取相应回应数据
  67.             HttpWebResponse response = (HttpWebResponse)request.GetResponse();
  68.             //直到request.GetResponse()程序才开始向目标网页发送Post请求
  69.             Stream instream = response.GetResponseStream();
  70.             StreamReader sr = new StreamReader(instream, Encoding.UTF8);
  71.             //返回结果网页(html)代码
  72.             string content = sr.ReadToEnd();
  73.             instream.Close();
  74.             return content;
  75.         }
  76.     }
  77. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!




欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/) Powered by Discuz! X3.4