首页 > 其他分享 >一个例子形象的理解协程和线程的区别

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

时间:2022-10-07 17:06:11浏览次数:46  
标签:协程 string int request System 例子 线程 using

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

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毫秒再测试,测试结果如下:

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


性能差10倍!把Form1.cs构造函数中添加一行ThreadPool.SetMinThreads(20, 20);再测:

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


设置线程池中线程的最小数量为20后,性能差距缩小了,性能只差4倍!为什么?没有设置线程池最小数量时,大约每1秒增加1到2个线程,线程增加速度太慢了,不影响协程性能,协程只需要很少的线程数量,但影响多线程性能。把Form1.cs构造函数中代码修改成ThreadPool.SetMinThreads(200, 200);再测:

一个例子形象的理解协程和线程的区别_服务端_04


当线程池中线程数量足够多时,性能差不多了!

结论

通过这个形象的例子,你体会到协程的好处了吗?
有人可能会说,你怎么不把WebAPI端改成异步试试?WebAPI端是模拟的操作,在没有外部操作(IO操作、数据库操作等),仅有数据计算时,WebAPI端改成异步没区别。

有一个截图中没有体验出来的,测试过程中,对于协程测试,工作线程和异步线程始终为0,我想异步线程应该是变化的,可能只是变化太快,看不出来。而多线程测试,测试过程中,我们可以看到工作线程的数量是大于0的,维持在一定数量,直到请求完成,也就是说,测试过程中,要占用一定数量的工作线程。

所以结论是什么?
协程在执行耗时请求时,不会占用线程(注意占用这个词,它肯定是使用线程的,但不会在耗时请求过程中占用),在线程池中线程数量较少时,协程的性能比多线程好很多。想一想,要是IO操作、数据库操作,存在一些慢查询、超时的,如果你使用多线程,你的线程池就爆了,协程就不会(Talk is cheap, show me the code!),后面附上测试。

WebAPI服务端补充说明

上面的测试,服务端我忘了说了,服务端启动服务前,我加了一行代码ThreadPool.SetMinThreads(200, 200);,因为你测试客户端之前,服务端性能要跟上,不然测了个寂寞。

如果我把这行代码删掉,预热后,再测:

一个例子形象的理解协程和线程的区别_服务端_05


可以看到差距只有2.5倍了!因为服务端线程数量此时是1秒增加1、2个线程,服务端性能跟不上,客户端的异步请求自然也快不起来。

爆线程池测试

测试前修改:

  1. 把WebAPI接口中模拟执行耗时操作改成2000000毫秒,模拟服务端性能不行,反应慢。
  2. ThreadPool.SetMinThreads(20, 20);客户端线程池最小线程数量设置为20,当然线程池越大越不容易爆,这里为了更快重现出来,所以设置小一点,注意,我可没有设置线程池上限!只是设置了下限。

测试视频:

注意看测试时工作线程数量:

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


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

一个例子形象的理解协程和线程的区别_服务端_07

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

一个例子形象的理解协程和线程的区别_服务端_08


注意看工作线程数量!

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;
}
}
}



标签:协程,string,int,request,System,例子,线程,using
From: https://blog.51cto.com/u_5496753/5734628

相关文章

  • Spring Boot 2.x基础教程:配置@Async异步任务的线程池
    上一篇我们介绍了如何使用@Async注解来创建异步任务,我可以用这种方法来实现一些并发操作,以加速任务的执行效率。但是,如果只是如前文那样直接简单的创建来使用,可能还是会碰......
  • 夯实Java基础,一篇文章全解析线程问题
    1\.线程是什么操作系统支持多个应用程序并发执行,每个应用程序至少对应一个进程,彼此之间的操作和数据不受干扰,彼此通信一般采用管道通信、消息队列、共享内存等方式。当一......
  • C/C++语言 MD5例子
    之前研究了一下在C中进行MD5加密,由于找了很久没有找到现成的库文件,所以所幸自己去写了一下。个人感觉C的便捷性没有Python好的原因就是这里。下面是我写的一个例子。mian......
  • 虚拟线程 - VirtualThread源码透视
    前提JDK19于2022-09-20发布GA版本,该版本提供了虚拟线程的预览功能。下载JDK19之后翻看了一下有关虚拟线程的一些源码,跟早些时候的Loom项目构建版本基本并没有很大出入,也跟......
  • 分布式场景中确保线程安全的解决方案,redis实现分布式锁
     1.死锁问题场景:当用redis做分布式锁时,当A用户竞争锁成功,A用户所在的主机挂了,这时候还没有来得及释放锁,那么其他用户去用setnx指令去竞争锁时发现redis有......
  • Java多线程(day2—重要关键词)
    Java多线程中的几个关键词Synchronized与ReentrantLock SynchronizedReentrantLock层次JVM层面的锁,是Java关键词JDK提供的,属于API层面的锁使用1.修饰实......
  • Java—多线程
    Java多线程基础概念进程与线程进程:操作系统分配资源的最小单位线程:CPU执行的最小单位线程分类1.用户线程用户自己创建的业务线程;2.守护线程......
  • DeepMind Lab的一些python例子—————(Ubuntu22.04系统安装DeepMind Lab)后续
    相关资料:Ubuntu22.04系统安装DeepMindLab ======================================================  importdeepmind_labimportnumpyasnp#Createane......
  • 线程类中的相关方法
    测试Thread中的常用方法:1,start():启动当前线程:调用当前线程的run()2,run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中3.currentThread():静态方法,返......
  • ThreadPoolExecutor使用和思考(上)-线程池大小设置与BlockingQueue的三种实现区别
    前记: jdk官方文档(javadoc)是学习的最好,最权威的参考。corePoolSize和maximumPoolSize,BlockingQueue选型(SynchronousQueue,​​LinkedBlockingQueue,​​​​ArrayBlockingQ......