dotnet 使用 NamedPipeClientStream 连接一个不存在管道服务名将不断空跑 CPU 资源
本文记录一个开发和代码审查过程中,需要关注的细节。在 dotnet 里,在 .NET 6 和以下版本,包括 .NET Framework 版本,使用 NamedPipeClientStream 进行连接管道服务,如果此时的管道服务没有存在,或者还没有启动,调用 ConnectAsync 或 Connect 方法,将会进入一个循环,不断进行空跑,等待超时或者是连接上。默认的 ConnectAsync 或 Connect 方法,传入的超时时间都是无穷,也就是将会无限重试,不断消耗 CPU 资源
咱可以使用 NamedPipeClientStream 去连接一个管道服务,从而建立多进程之间的通讯。在连接时,最好是先有管道服务启动,然后再启动管道客户端 NamedPipeClientStream 进行连接。因为如果在 NamedPipeClientStream 开始 Connect 时,还不存在管道服务,那将有一段时间进行 CPU 的空跑
不过好在 Connect 底层实现上,采用了 SpinWait 的 SpinOnce 方法进行自旋,此自旋是一个混合自旋方式,当次数多的时候,将会自动出让 CPU 执行权。但是如果等待连接的数量足够多,依然会占用一定的 CPU 资源,占用多少,取决于 CPU 的价格(价格约等于性能)哈。如以下的代码,将会不断去连接一个不存在的管道名
using System.IO.Pipes;
using System.Security.Principal;
for (int i = 0; i < 1000; i++)
{
var namedPipeClientStream = new NamedPipeClientStream(".", "NotExists_" + i, PipeDirection.Out,
PipeOptions.None, TokenImpersonationLevel.Impersonation);
// Task.Factory.StartNew(namedPipeClientStream.Connect, TaskCreationOptions.LongRunning);
_ = namedPipeClientStream.ConnectAsync();
}
Console.Read();
尝试运行以上的代码,可以看到 CPU 将会不断上升。使用 ConnectAsync 版本,线程数量上升较慢,同时 CPU 上升速度也较慢。如使用被注释的 Task.Factory.StartNew(namedPipeClientStream.Connect, TaskCreationOptions.LongRunning)
代码,那可以看到 CPU 将会快速被占用,线程也有大量的数量
因此在开发的时候,如果需要使用 NamedPipeClientStream 进行 Connect 或 ConnectAsync 连接,除非能明确管道的服务端已创建成功,否则都推荐加上超时逻辑。不然,在尝试连接一个不存在的服务管道名,将会占用线程,不断空跑。数量少的时候,没有什么影响,数量多的时候,将会浪费 CPU 资源
如果关心 .NET 的底层实现,为什么会有此问题,请继续阅读
在 .NET 6 和以下版本,包括 .NET Framework 版本,使用 NamedPipeClientStream 的 ConnectAsync 方法,本质上相当于使用 Task.Run
包一个 Connect 方法,如以下的 .NET 6 有删减的代码。为了让本文清晰,本文以下就只讨论使用 Connect 方法的逻辑
public sealed partial class NamedPipeClientStream : PipeStream
{
// 为了让文章清晰,删减部分代码
public void Connect()
{
Connect(Timeout.Infinite);
}
public void Connect(int timeout)
{
ConnectInternal(timeout, CancellationToken.None, Environment.TickCount);
}
public Task ConnectAsync()
{
// We cannot avoid creating lambda here by using Connect method
// unless we don't care about start time to be measured before the thread is started
return ConnectAsync(Timeout.Infinite, CancellationToken.None);
}
public Task ConnectAsync(int timeout, CancellationToken cancellationToken)
{
int startTime = Environment.TickCount; // We need to measure time here, not in the lambda
return Task.Run(() => ConnectInternal(timeout, cancellationToken, startTime), cancellationToken);
}
private void ConnectInternal(int timeout, CancellationToken cancellationToken, int startTime)
{
// 连接的代码
}
}
通过如上代码可以了解到,实际的连接代码是放在 ConnectInternal 方法里面。在 .NET Framework 下的代码也是差不多的,细节可以忽略
在 ConnectInternal 方法里面,将会进入一个循环,此循环的退出条件只有超时
private void ConnectInternal(int timeout, CancellationToken cancellationToken, int startTime)
{
// This is the main connection loop. It will loop until the timeout expires.
int elapsed = 0;
SpinWait sw = default;
do
{
cancellationToken.ThrowIfCancellationRequested();
// Determine how long we should wait in this connection attempt
int waitTime = timeout - elapsed;
if (cancellationToken.CanBeCanceled && waitTime > CancellationCheckInterval)
{
waitTime = CancellationCheckInterval;
}
// Try to connect.
if (TryConnect(waitTime, cancellationToken))
{
return;
}
// Some platforms may return immediately from TryConnect if the connection could not be made,
// e.g. WaitNamedPipe on Win32 will return immediately if the pipe hasn't yet been created,
// and open on Unix will fail if the file isn't yet available. Rather than just immediately
// looping around again, do slightly smarter busy waiting.
sw.SpinOnce();
}
while (timeout == Timeout.Infinite || (elapsed = unchecked(Environment.TickCount - startTime)) < timeout);
throw new TimeoutException();
}
如果调用无参方法,如上面代码,那传入的超时时间是无穷,此时相当于无限循环。在 TryConnect 方法里面,将会尝试连接传入的服务管道名,然而在服务管道没有启动时,是连接不到的,于是 TryConnect 将返回失败。如上面代码,将会进入下一次循环
好在进入循环之前,将会调用 SpinOnce 方法进行自旋。但是无论如何,在连接一个不存在的管道名且没有设置超时时间,将会导致线程进行无限空跑
使用 ConnectAsync 方法时,将使用 Task.Run
方法包装,如果此时的连接一个不存在的管道名且没有设置超时时间,将导致当前的线程池的当前执行线程进入无限循环空跑,浪费此线程。而 Task.Run
方法将会从线程池调度出一个线程来执行,如果此线程执行了很长时间都没有返回,那么线程池在线程不够用的时候,将会再启动一个新的线程。这就是为什么本文一开始的代码里面,使用 ConnectAsync 方法,将会让 CPU 缓慢上升和线程缓慢上升
可以通过如下方式获取本文的源代码,先创建一个空文件夹,接着使用命令行 cd 命令进入此空文件夹,在命令行里面输入以下代码,即可获取到本文的代码
git init
git remote add origin https://gitee.com/lindexi/lindexi_gd.git
git pull origin aacbea5980daa44cabb57d3edde4e1848fe4f8dd
以上使用的是 gitee 的源,如果 gitee 不能访问,请替换为 github 的源
git remote remove origin
git remote add origin https://github.com/lindexi/lindexi_gd.git
获取代码之后,进入 KejeakelcoloqeNemkegudaka 文件夹
dotnet 使用 NamedPipeClientStream 连接一个不存在管道服务名将不断空跑 CPU 资源的更多相关文章
- SQL Server 连接问题-命名管道
原文:SQL Server 连接问题-命名管道 出自:http://blogs.msdn.com/b/apgcdsd/archive/2011/01/12/sql-server-1.aspx 一.前言 ...
- 最简单的启动并连接一个redis的docker容器
启动一个容器: $ sudo docker run --name <name> -d redis 连接一个容器: sudo docker run -it --link <name&g ...
- 开源一个跨平台运行的服务插件 - TaskCore.MainForm
本次将要很大家分享的是一个跨平台运行的服务插件 - TaskCore.MainForm,此框架是使用.netcore来写的,现在netcore已经支持很多系统平台运行了,所以将以前的Task.Main ...
- [C语言]一个很实用的服务端和客户端进行TCP通信的实例
本文给出一个很实用的服务端和客户端进行TCP通信的小例子.具体实现上非常简单,只是平时编写类似程序,具体步骤经常忘记,还要总是查,暂且将其记下来,方便以后参考. (1)客户端程序,编写一个文件clie ...
- [ZooKeeper.net] 1 模仿dubbo实现一个简要的http服务的注册 基于webapi
今天来试着模仿下dubbo实现一个简要的http服务的注册,虽说是模仿不过是很廉价的那种,只是模仿了一点点点...... 先放上demo目录结构: 开头还是把ZooKeeper的一些简要介绍搬过来看看 ...
- 如何创建一个标准的Windows服务
出处:http://www.cnblogs.com/wuhuacong/archive/2009/02/11/1381428.html 如何创建一个标准的Windows服务 在很多时候,我们需要一个定 ...
- 连接postgres特别消耗cpu资源而引发的PostgreSQL性能优化考虑
由于是开发阶段,所以并没有配置postgres的参数,都是使用安装时的默认配置,以前运行也不见得有什么不正常,可是前几天我的cpu资源占用突然升高.查看进程,发现有一个postgres的进程占用CPU ...
- 学习构建一个简单的wcf服务
入门,构建第一个WCF程序 1.服务端 建立一个控制台应用程序作为Server,新建一个接口IData作为服务契约.这个契约接口一会儿也要放到Client端,这样双方才能遵循相同的标准.别忘了添加对 ...
- 5G RRC——为NAS层提供连接管理,消息传递等服务; 对接入网的底层协议实体提供参数配置的功能; 负责UE移动性管理相关的测量、控制等功能
from:http://www.cnblogs.com/kkdd-2013/p/3868676.html 1 RRC协议功能 为NAS层提供连接管理,消息传递等服务: 对接入网的底层协议实体提供参数配 ...
- 超详细,新手都能看懂 !使用SpringBoot+Dubbo 搭建一个简单的分布式服务
来自:JavaGuide Github 地址:https://github.com/Snailclimb/springboot-integration-examples 目录: 使用 SpringBo ...
随机推荐
- L7结合Turf.js实现空间分析与数据可视化
1. 概述 AntV L7 是蚂蚁集团 AntV 数据可视化团队推出的基于 WebGL 的开源大规模地理空间数据可视分析引擎,其特点是通过简单的代码进行配置,即可在前端网页中绘制精美的地图以及相关的图 ...
- JQ实现音乐插件并自动播放
这里分享我最近写出来的一个小东西,基于jq的音乐播放器,可以嵌套到网站 效果截图: 具体首页代码如下: <!DOCTYPE html> <html> <head> ...
- 摄影系列:李涛ps视频教程笔记
四种颜色模式: HSB:人眼的识别. RGB:基于光.(RGB自然三原色,三个最大值,得出白色,所以RGB为加色模式) CMY:基于印刷.(青.品.黄印刷三原色,三个最大值,得出黑色,所以CMY为减色 ...
- Kingbase ES 函数返回-return语句
文章概要: 本文在https://www.cnblogs.com/kingbase/p/15703611.html 一文的基础上总结了Kingbase ES中函数能支持的return语句,整体上兼容o ...
- CVS Excell乱码怎么修改
问题描述 日常办公,从网页或者其他web端下载的表格(excel格式或csv格式),打开后发现中文乱码,如下: 常规解法 百度会发现,大部分提供的方法,就是通过excel的另存为,然后选择相应的编码, ...
- Python爬虫初步---jupyterNptebook使用
学习视频笔记:
- Docker容器编排技术解析与实践
本文全面探索了容器编排技术的核心概念.工具和高级应用,包括Docker Compose.Kubernetes等主要平台及其高级功能如网络和存储管理.监控.安全等.此外,文章还探讨了这些技术在实际应用中 ...
- #树状数组,哈希#洛谷 6687 论如何玩转 Excel 表格
题目 分析 首先一列的数不会发生变化,只是交换列, 并且交换列的时候奇数列变成偶数列取反, 偶数列变成奇数列取反,考虑直接将偶数列全部取反, 那只需要交换列就可以了,奇数列交换到偶数列会取反, 奇数列 ...
- Jetty的http-forwarded模块
启用http-forwarded模块,执行如下命令: java -jar $JETTY_HOME/start.jar --add-modules=http-forwarded 命令的输出,如下: IN ...
- OpenHarmony父子组件双项同步使用:@Link装饰器
子组件中被@Link装饰的变量与其父组件中对应的数据源建立双向数据绑定. 说明: 从API version 9开始,该装饰器支持在ArkTS卡片中使用. 概述 @Link装饰的变量与其父组件中的数 ...