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

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

问题

作者:知乎用户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代码

  1. static NUM: i64 = 0;
  2. async fn fff() {
  3. let t = tokio::time::Duration::from_millis(15);
  4. loop {
  5. tokio::time::sleep(t).await;
  6. if NUM > 1000 {
  7. println!("大于");
  8. }
  9. }
  10. }
  11. #[tokio::main]
  12. async fn main() {
  13. let mut i = 0;
  14. while i < 10000 {
  15. tokio::task::spawn(fff());
  16. i = i + 1;
  17. }
  18. println!("over");
  19. let mut s = String::new();
  20. std::io::stdin().read_line(&mut s).unwrap();
  21. }

go代码

  1. package main
  2. import (
  3. "fmt"
  4. "time"
  5. )
  6. var AAA int
  7. func fff() {
  8. for {
  9. time.Sleep(time.Millisecond * 15)
  10. if AAA > 10000 {
  11. fmt.Println("大于")
  12. }
  13. }
  14. }
  15. func main() {
  16. for i := 0; i < 10000; i++ {
  17. go fff()
  18. }
  19. fmt.Println("begin")
  20. var s string
  21. fmt.Scanln(&s)
  22. }

c#代码

  1. internal class Program
  2. {
  3. static Int64 num = 0;
  4. static async void fff()
  5. {
  6. while (true)
  7. {
  8. await Task.Delay(15);
  9. if (num > 100000)
  10. Console.WriteLine("大于");
  11. }
  12. }
  13. static void Main()
  14. {
  15. for (int i = 0; i < 10000; i++)
  16. fff();
  17. Console.WriteLine("begin");
  18. Console.ReadLine();
  19. }
  20. }

我的测试

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

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

我的尝试

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

  1. int num = 0;
  2. async void func(int i)
  3. {
  4. int n = 25; // 无延迟干活次数
  5. int m = 1; // 干n次活,m次延迟干活
  6. int t = 500; // 延迟干活时间,根据具体业务设置可以接受的延迟时间
  7. long count = 0;
  8. while (true)
  9. {
  10. if (count < n)
  11. {
  12. await Task.CompletedTask;
  13. }
  14. else if (count < n + m)
  15. {
  16. await Task.Delay(t); // 循环执行了若干次,休息一会,把机会让给其它循环,毕竟CPU就那么多
  17. }
  18. else
  19. {
  20. count = 0;
  21. }
  22. count++;
  23. Interlocked.Increment(ref num); // 干活
  24. }
  25. }
  26. for (int i = 0; i < 10000; i++)
  27. {
  28. func(i);
  29. }
  30. _ = Task.Factory.StartNew(() =>
  31. {
  32. Stopwatch sw = Stopwatch.StartNew();
  33. while (true)
  34. {
  35. Thread.Sleep(5000);
  36. double speed = num / sw.Elapsed.TotalSeconds;
  37. Console.WriteLine($"10000个循环干活总速度={speed:#### ####.0} 次/秒");
  38. }
  39. }, TaskCreationOptions.LongRunning);
  40. Console.WriteLine("begin");
  41. Console.ReadLine();

再次尝试

  1. using System.Collections.Concurrent;
  2. using System.Diagnostics;
  3. using System.Runtime.CompilerServices;
  4. int num = 0;
  5. MyTimer myTime = new MyTimer(15, 17000);
  6. async void func(int i)
  7. {
  8. while (true)
  9. {
  10. await myTime.Delay();
  11. // Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss.ffff} - {i}");
  12. Interlocked.Increment(ref num); // 干活
  13. }
  14. }
  15. for (int i = 0; i < 10000; i++)
  16. {
  17. func(i);
  18. }
  19. _ = Task.Factory.StartNew(() =>
  20. {
  21. Stopwatch sw = Stopwatch.StartNew();
  22. while (true)
  23. {
  24. Thread.Sleep(5000);
  25. double speed = num / sw.Elapsed.TotalSeconds;
  26. Console.WriteLine($"10000个循环干活总速度={speed:#### ####.0} 次/秒");
  27. }
  28. }, TaskCreationOptions.LongRunning);
  29. Console.WriteLine("开始");
  30. Console.ReadLine();
  31. myTime.Dispose();
  32. class MyTimer : IDisposable
  33. {
  34. private int _interval;
  35. private Thread _thread;
  36. private bool _threadRunning = false;
  37. private ConcurrentQueue<MyAwaiter> _queue;
  38. /// <summary>
  39. /// Timer
  40. /// </summary>
  41. /// <param name="interval">时间间隔</param>
  42. /// <param name="parallelCount">并行数量</param>
  43. public MyTimer(int interval, int parallelCount)
  44. {
  45. _interval = interval;
  46. _queue = new ConcurrentQueue<MyAwaiter>();
  47. _threadRunning = true;
  48. _thread = new Thread(() =>
  49. {
  50. while (_threadRunning)
  51. {
  52. for (int i = 0; i < parallelCount; i++)
  53. {
  54. if (_queue.TryDequeue(out MyAwaiter myAwaiter))
  55. {
  56. myAwaiter.Run();
  57. }
  58. }
  59. Thread.Sleep(_interval);
  60. }
  61. });
  62. _thread.Start();
  63. }
  64. public MyAwaiter Delay()
  65. {
  66. MyAwaiter awaiter = new MyAwaiter(this);
  67. _queue.Enqueue(awaiter);
  68. return awaiter;
  69. }
  70. public void Dispose()
  71. {
  72. _threadRunning = false;
  73. }
  74. }
  75. class MyAwaiter : INotifyCompletion
  76. {
  77. private MyTimer _timer;
  78. private Action _continuation;
  79. public bool IsCompleted { get; private set; }
  80. public MyAwaiter(MyTimer timer)
  81. {
  82. _timer = timer;
  83. }
  84. public void OnCompleted(Action continuation)
  85. {
  86. _continuation = continuation;
  87. }
  88. public void Run()
  89. {
  90. IsCompleted = true;
  91. _continuation?.Invoke();
  92. }
  93. public MyAwaiter GetAwaiter()
  94. {
  95. return this;
  96. }
  97. public object GetResult()
  98. {
  99. return null;
  100. }
  101. }

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

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

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

我给的回答:

  1. for (int i = 0; i < 500; i++)
  2. {
  3. ReadPLC(i);
  4. }
  5. async void ReadPLC(int plcIndex)
  6. {
  7. while (true)
  8. {
  9. // todo: 读取PLC
  10. Console.WriteLine($"读取PLC {plcIndex}");
  11. await Task.Delay(200);
  12. }
  13. }

还好它这只要求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. 解决IDEA加载maven工程缓慢

    如图,哪里没有加哪里 -DarchetypeCatalog=internal

  2. JavaWeb项目练习(学生选课管理系统)三【登录功能】

    需求: 首页为用户登录页面,管理员,教师,学生三种角色用户登录后,进入相应的功能页. 在index.jsp文件里跳转到login.jsp页面,为了更好地书写 <%@ page contentTy ...

  3. [GDOI22pj1A] 邹忌讽秦王纳谏

    时间空间限制: 1 秒, 256 MB 齐国人邹忌对齐国国君齐威王说,大王身边的人会因为私情.利益等原因而对大王阿谀奉承,所以不能光听好话,只有广泛接受群众的批评意见,才不会被蒙蔽双眼,齐国才能强盛. ...

  4. smm整合

    配置整合 这个里面SpringConfig 就是书写Spring的配置类,其中加载了jdbc配置类和mybatis配置类,还加载了jdbc资源类. package com.itheima.config ...

  5. MySQL 8.0.32 InnoDB ReplicaSet 配置和手动切换

    1.环境准备 主库:192.168.137.4 mytest3 从库:192.168.137.5 mytest4 MySQL: 8.0.32 2.配置 ReplicaSet 实例 启动 mysql s ...

  6. 【开源项目推荐】OpenMetadata——基于开放元数据的一体化数据治理平台

    大家好,我是独孤风. 这几年数据治理爆火,但迟迟没有一个优秀的开源数据治理平台的出现.很多公司选择元数据管理平台作为基础,再构建数据质量,数据血缘等工具. 今天为大家推荐的开源项目,是一个一体化的数据 ...

  7. 【Datahub系列教程】Datahub入门必学——DatahubCLI之Docker命令详解

    大家好,我是独孤风,今天的元数据管理平台Datahub的系列教程,我们来聊一下Datahub CLI.也就是Datahub的客户端. 我们在安装和使用Datahub 的过程中遇到了很多问题. 如何安装 ...

  8. adobe全家桶破解网站

    原文链接:https://baiyunju.cc/8602 总有一些国内.外的大神在破解Adobe全家桶软件,包括Windows.Mac系统最新版的2021.2022版PS.AI.PR.PL.ME.I ...

  9. vue-admin-template动态菜单后台获取菜单

    vue-admin-template.vue-element-admin配置动态菜单,菜单数据从后台获取. 我在网上search了几个小时也没有找到想要的emm,翻官网也没有说明,只说明了路由覆盖.只 ...

  10. [Python急救站]学生管理系统链接数据库

    相信很多人在初学Python的时候,经常最后作业就是完成一个学生管理系统,但是我们来做一个完美的学生管理系统,并且将数据储存到数据库里. 我们先看看我们的数据库怎么设置. 首先呢,我选择用的是SQL ...