知乎上有人提了个问题,可惜作者已把账号注销了。

复制一下他的问题,仅讨论技术用,侵删。

问题

作者:知乎用户fLP2gX

链接:https://www.zhihu.com/question/634840187/answer/3328710757

来源:知乎

著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

最近遇见个需求,需要开2000个线程无限循环,每个循环有sleep(1),这个在其他语言很容易实现,在c#中就很难了,我试过task.delay(1)直接一秒钟10次gc。今天有空测试下多种语言的协程,都是开10000个协程无限循环,中间有个sleep(15ms), cpu使用率rust 40%,golang 3%,c# 16%, 都是release,把我搞不自信了。cpu是11代i5 ,rust的开销简直无法忍受。为了严谨测试了系统线程,cpu使用率43%

rust代码

static NUM: i64 = 0;
async fn fff() {
let t = tokio::time::Duration::from_millis(15);
loop {
tokio::time::sleep(t).await;
if NUM > 1000 {
println!("大于");
}
}
}
#[tokio::main]
async fn main() {
let mut i = 0;
while i < 10000 {
tokio::task::spawn(fff());
i = i + 1;
}
println!("over");
let mut s = String::new();
std::io::stdin().read_line(&mut s).unwrap();
}

go代码

package main
import (
"fmt"
"time"
)
var AAA int
func fff() {
for {
time.Sleep(time.Millisecond * 15)
if AAA > 10000 {
fmt.Println("大于")
}
}
}
func main() {
for i := 0; i < 10000; i++ {
go fff()
}
fmt.Println("begin")
var s string
fmt.Scanln(&s)
}

c#代码

internal class Program
{
static Int64 num = 0;
static async void fff()
{
while (true)
{
await Task.Delay(15);
if (num > 100000)
Console.WriteLine("大于");
}
}
static void Main()
{
for (int i = 0; i < 10000; i++)
fff();
Console.WriteLine("begin");
Console.ReadLine();
}
}

我的测试

我使用Task.Delay测试,发现速度只有30多万次/秒,然后CPU占用达到30%。

然后我又上网了找了一个时间轮算法HashedWheelTimer,使用它的Delay,经过调参,速度可以达到50多万次/秒,达到了题主的要求,但CPU占用依然高达30%。我不知道是不是我找的这个HashedWheelTimer写的不好。

我的尝试

如下代码勉强达到了题主的要求,速度可以达到50多万次/秒,CPU占用8%,比go的3%要高一些,但比用Task.Delay要好很多了。但有个缺点,就是任务延迟可能会高达500毫秒。

int num = 0;

async void func(int i)
{
int n = 25; // 无延迟干活次数
int m = 1; // 干n次活,m次延迟干活
int t = 500; // 延迟干活时间,根据具体业务设置可以接受的延迟时间
long count = 0;
while (true)
{
if (count < n)
{
await Task.CompletedTask;
}
else if (count < n + m)
{
await Task.Delay(t); // 循环执行了若干次,休息一会,把机会让给其它循环,毕竟CPU就那么多
}
else
{
count = 0;
}
count++; Interlocked.Increment(ref num); // 干活
}
} for (int i = 0; i < 10000; i++)
{
func(i);
} _ = Task.Factory.StartNew(() =>
{
Stopwatch sw = Stopwatch.StartNew();
while (true)
{
Thread.Sleep(5000);
double speed = num / sw.Elapsed.TotalSeconds;
Console.WriteLine($"10000个循环干活总速度={speed:#### ####.0} 次/秒");
}
}, TaskCreationOptions.LongRunning); Console.WriteLine("begin");
Console.ReadLine();

再次尝试

using System.Collections.Concurrent;
using System.Diagnostics;
using System.Runtime.CompilerServices; int num = 0;
MyTimer myTime = new MyTimer(15, 17000); async void func(int i)
{
while (true)
{
await myTime.Delay();
// Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.ffff} - {i}"); Interlocked.Increment(ref num); // 干活
}
} for (int i = 0; i < 10000; i++)
{
func(i);
} _ = Task.Factory.StartNew(() =>
{
Stopwatch sw = Stopwatch.StartNew();
while (true)
{
Thread.Sleep(5000);
double speed = num / sw.Elapsed.TotalSeconds;
Console.WriteLine($"10000个循环干活总速度={speed:#### ####.0} 次/秒");
}
}, TaskCreationOptions.LongRunning); Console.WriteLine("开始");
Console.ReadLine();
myTime.Dispose(); class MyTimer : IDisposable
{
private int _interval;
private Thread _thread;
private bool _threadRunning = false;
private ConcurrentQueue<MyAwaiter> _queue; /// <summary>
/// Timer
/// </summary>
/// <param name="interval">时间间隔</param>
/// <param name="parallelCount">并行数量</param>
public MyTimer(int interval, int parallelCount)
{
_interval = interval;
_queue = new ConcurrentQueue<MyAwaiter>();
_threadRunning = true; _thread = new Thread(() =>
{
while (_threadRunning)
{
for (int i = 0; i < parallelCount; i++)
{
if (_queue.TryDequeue(out MyAwaiter myAwaiter))
{
myAwaiter.Run();
}
} Thread.Sleep(_interval);
}
});
_thread.Start();
} public MyAwaiter Delay()
{
MyAwaiter awaiter = new MyAwaiter(this);
_queue.Enqueue(awaiter);
return awaiter;
} public void Dispose()
{
_threadRunning = false;
}
} class MyAwaiter : INotifyCompletion
{
private MyTimer _timer; private Action _continuation; public bool IsCompleted { get; private set; } public MyAwaiter(MyTimer timer)
{
_timer = timer;
} public void OnCompleted(Action continuation)
{
_continuation = continuation;
} public void Run()
{
IsCompleted = true;
_continuation?.Invoke();
} public MyAwaiter GetAwaiter()
{
return this;
} public object GetResult()
{
return null;
} }

时间轮算法有点难写,我还没有掌握,换了一种写法,达到了题主的要求,速度可以达到50多万次/秒,CPU占用3%。但有缺点,MyTimer用完需要Dispose,有个并行度参数parallelCount需要根据测试代码中for循环次数设置,设置为for循环次数的1.7倍,这个参数很讨厌,再一个就是Delay时间设置了15毫秒,但是不精确,实际任务延迟可能会超出15毫秒,或者小于15毫秒,当然这里假设计时器是精确的,实际上计时器误差可能到达10毫秒,这里认为它是精确无误差的,在这个前提下,实际上会有一些误差,但比上次尝试,最大延迟500毫秒应该要好很多。

本人水平有限,写的匆忙,但我感觉这个问题还是很重要的。问题简单来说就是大量Task.Delay会导致性能问题,有没有更高效的Delay实现?

这个问题有什么实际价值?看我另一个回答:求助多线程读取大量PLC问题?

我给的回答:

for (int i = 0; i < 500; i++)
{
ReadPLC(i);
} async void ReadPLC(int plcIndex)
{
while (true)
{
// todo: 读取PLC
Console.WriteLine($"读取PLC {plcIndex}"); await Task.Delay(200);
}
}

还好它这只要求500个plc,如果是1万个plc呢?如果要求Delay(15),就不能像我这样写了。但是,你看看,这样写有多么简单?!本来一个多线程并行问题,写起来很复杂,很容易写出bug,如果能像同步代码这样写,写出来性能不亚于多线程并行,逻辑简单,不容易出bug。

C# golang 开10000个无限循环的性能的更多相关文章

  1. 详细分析Android viewpager 无限循环滚动图片

    由于最近在忙于项目,就没时间更新博客了,于是趁着周日在房间把最近的在项目中遇到的技术总结下.最近在项目中要做一个在viewpager无限滚动图片的需求,其实百度一下有好多的例子,但是大部分虽然实现了, ...

  2. 使用 iscroll 实现焦点图无限循环

    现在大家应该都看到过焦点图轮播的效果,这个效果是什么样我就不截图了.昨天做练习,练习要求是使用iscroll实现焦点图的无限循环滚动,并且当手指触摸焦点图后,停止焦点图的循环滚动.第一次接触iscro ...

  3. c#无限循环线程如何正确退出

    c#无限循环线程如何正确退出 在主程序将要结束时,迅速正确退出无限循环执行的子线程.一般子线程循环执行会有一个指定的周期, 在子线程等待(或者睡眠)时,无法唤醒退出,尤其在执行周期较长时,子线程无法即 ...

  4. 自定义完美的ViewPager 真正无限循环的轮播图

    网上80%的思路关于Android轮播图无限循环都是不正确的,不是真正意义上的无限循环, 其思路大多是将ViewPager的getCount方法返回值设置为Integer.MAX_VALUE, 然后呢 ...

  5. 黑客整人代码,vbS整人代码大全(强制自动关机、打开无数计算器、无限循环等)

    vbe与vbs整人代码大全,包括强制自动关机.打开无数计算器.无限循环等vbs整人代码,感兴趣的朋友参考下.vbe与vbs整人代码例子:set s=createobject("wscript ...

  6. 一行代码引入 ViewPager 无限循环 + 页码显示

    (出处:http://www.cnblogs.com/linguanh) 前序: 网上的这类 ViewPager 很多,但是很多都不够好,体现在 bug多.对少页面不支持,例如1~2张图片.功能整合不 ...

  7. iOS开发系列--无限循环的图片浏览器

    --UIKit之UIScrollView 概述 UIKit框架中有大量的控件供开发者使用,在iOS开发中不仅可以直接使用这些控件还可以在这些控件的基础上进行扩展打造自己的控件.在这个系列中如果每个控件 ...

  8. iOS无限循环滚动scrollview

    经常有园友会问"博主,有没有图片无限滚动的Demo呀?", 正儿八经的图片滚动的Demo我这儿还真没有,今天呢就封装一个可以在项目中直接使用的图片轮播.没看过其他iOS图片无限轮播 ...

  9. iOS开发——高级篇——图片轮播及其无限循环效果

    平时APP中的广告位.或者滚动的新闻图片等用到的就是图片轮播这种效果,实现方式主要有两种,一种是ScrollView+ImageView,另一种则是通过CollectionView,今天总结的是Scr ...

  10. android 无限循环的viewpager

    思路 例如存在 A -B -C 需要在viewpager滑动时无限循环 1.我们可以设计 C' A B C A'  C'与C相同,A'与A相同 2.滑动到A'时,则index回到1 3.滑动到C'时, ...

随机推荐

  1. C语言计算并输出华氏温度为80F所对应的摄氏温度C。转换公式为:C=5*(F-32)/9

    #include <stdio.h> int main() { double F = 80.0, C;//定义摄氏温度变量,赋值华氏温度 C = 5 * (F - 32) / 9.0;// ...

  2. 实现金蝶云星空与赛意SMOM系统的无缝数据对接

    1. 金蝶云星空:为运营协同与管控型企业提供通用ERP服务平台 金蝶云星空是基于当今先进管理理论和数十万家国内客户最佳应用实践开发的ERP服务平台.它针对事业部制.多地点.多工厂等企业和集团公司,提供 ...

  3. ERP大作业进度(一)

    ERP和进销存的区别 ERP(企业资源计划)和进销存(进货.销售和库存管理)是两个不同的概念,尽管它们在企业管理中通常存在交集.以下是它们之间的主要区别: 范围: ERP:ERP系统是一个综合性的.集 ...

  4. 【python】Tkinter学习

    定义 python自带的可编辑的GUI界面,是一个图像窗口. Tkinter是使用python进行窗口视窗设计的模块. 标签-按钮 2.1.Lable&Button标签和按钮 定义window ...

  5. socket链接和发送demo

    Socker 包是创建客户端的,用于链接服务器: ServerSocket 包是创建服务器的,启动端口进行监听等待链接 socket客户端-----------------java.lang.Stri ...

  6. Spring+SpringMVC+Mybatis+Vue+Axios+Thymeleaf整合案例

    1.创建SSM-Parent父项目 修改配置文件 <?xml version="1.0" encoding="UTF-8"?> <projec ...

  7. offline RL | Pessimistic Bootstrapping (PBRL):在 Q 更新中惩罚 uncertainty,拉低 OOD Q value

    论文题目:Pessimistic Bootstrapping for Uncertainty-Driven Offline Reinforcement Learning,ICLR 2022,6 6 8 ...

  8. 【UniApp】-uni-app-项目实战页面布局(苹果计算器)

    前言 经过前面的文章介绍,基本上 UniApp 的内容就介绍完毕了 那么从本文开始,我们就开始进行一个项目的实战 这次做的项目是苹果计算器,这个项目的难度不是很大,但是也不是很简单,适合练手 创建项目 ...

  9. 年度盘点,四年的精华合集「GitHub 热点速览」

    今年是 GPT 年,无论是 GitHub 还是朋友圈还是技术平台,即便你不关心 GPT 的发展情况,同大模型.AI 相关的项目总能进入你的信息流.到这期为止,热度速览也连载了四年,从一开始习惯看 Gi ...

  10. Springboot3核心特性

    一.简介 1. 前置知识 Java17 Spring.SpringMVC.MyBatis Maven.IDEA 2. 环境要求 环境&工具 版本(or later) SpringBoot 3. ...