• 一个例子形象的理解协程和线程的区别


    一个例子形象的理解协程和线程的区别

    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;
            }
        }
    }
    
  • 相关阅读:
    vim使用基础
    linux基本命令随笔
    linux学习笔记
    中台建设随笔
    数据密集型系统响应优化
    TCP断开连接的问题
    多渠道接入系统总结
    关于实践的认识
    博客说明
    python下载图片的问题思考
  • 原文地址:https://www.cnblogs.com/s0611163/p/16593812.html
Copyright © 2020-2023  润新知