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 ...
随机推荐
- 07.Java类加载问题
目录介绍 7.0.0.1 Java内存模型里包含什么?程序计数器的作用是什么?常量池的作用是什么? 7.0.0.2 什么是类加载器?类加载器工作机制是什么?类加载器种类?什么是双亲委派机制? 7.0. ...
- 应急响应靶机训练-Linux1
靶机来源: 知攻善防实验室公众号 https://mp.weixin.qq.com/s/gCWGnBiwbqSnafXU1apJCA 我是在另一台主机上通过ssh连接到靶机进行解题的,以下为解题记录. ...
- 记录-html-docs-js避坑指南
这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前言 我们公司目前在做基于tiptap的在线协同文档,最近需要做导出 pdf.word 需求. 导出 word 文档使用的是html-do ...
- 应急响应靶机训练-Linux2
靶机来源: 知攻善防实验室公众号 https://mp.weixin.qq.com/s/xf2FgkrjZg-yWlB9-pRXvw 我是在另一台主机上通过ssh连接到靶机进行解题的,我的ip为192 ...
- Avalonia发布MacOS运行程序
1 打开xxx.csproj项目文件,添加Dotnet.Bundle包: <PackageReference Include="Dotnet.Bundle" Version= ...
- VScode 配置私钥免密登录
VScode 配置私钥免密登录 配置公钥私钥进行免密登录在前文已经提及.在完成上述配置后,我们希望在VScode中配置,毕竟主要的开发环境还是在VScode上且连接到远程服务器会经常遇到网络不稳定需要 ...
- module的定义及端口的作用
模型功能 module是verilog中层次划分的基本单元 通过module之间的调用,可以实现硬件描述层次的提高 端口列表则是module的输入输出,和数字电路的走线连接等效 基于module的不断 ...
- BorderDet:通过边界特征大幅提升检测准确率,即插即用且速度不慢 | ECCV 2020 Oral
边界对于定位问题十分重要,BorderDet的核心思想BorderAlign巧妙又有效,将边界特征融入到目标定位预测中,而且能够简单地融入到各种目标检测算法中带来较大的性能提升下.在开源实现中,对Bo ...
- 算法学习笔记【5】| ST表
ST表 Part 1:ST表解决的问题是什么 ST 表可以用来解决RMQ(区间最值问题)等可重复贡献的问题. ST表基于倍增的思想来实现. Part 2:ST表的实现 ST表通过 O(nlogn)& ...
- SpringBoot配置启动页(首页)控制台打印项目访问入口url
一.SpringBootApplication 1 package com.reliable.yang; 2 3 /** 4 * @author Administrator 5 * @date 202 ...