首页 > 代码库 > CPU多核控速
CPU多核控速
初学者很多对自己开发的软件使用硬件资源的时候并不注意,造成写出的东西不是很满意。
一般有两种情况:
1.写的都是同步单线程任务,不管你电脑有多少个核都不关我事 我就用你1个核所以不管怎么样都不会把CPU吃满。
这样的例子还是比较多的,在CPU出多核之后很多软件陆续已经将这些问题进行了修复,不过也还有这样的情况。
拿我常用的金山快盘同步盘这个软件来说吧,他在要进行一次同步任务前要先下载一份服务器端文件版本信息的比对数据,所以当我点立即同步的时候或者刚打开软件的时候他会马上使用网络进行下载(任务管理器或很多流量监控软件都可以监控到),下载完成后进行文件版本对比任务找出需要更新的文件,这个时候在 任务管理器 里的进程中可以看到金山快盘会很快占用300-500M左右的内存(这个视个人电脑硬件配置和这个金山快盘用户文件多少而变化)CPU利用率立马飙到50%(我用的这台电脑是双核的)而且只有一个核的利用率到达很高另一个核在睡觉。这种状态一直持续很长一段时间不怎么变化(可能我的文件数量比较多所以这个时间就会比较长)。
2.多线程任务不控制CPU利用率。
很多朋友写程序的时候可能比较少用到多任务多线程开发 或者 搞不好这个程序中的多任务多线程只会用一会儿而且很快就能完成这个作业,所以用到的时候也没注意这么多。
经常使用多任务多线程开发的朋友应该是比较清楚的,不需要看这个了;
咱们先看一下控速与不控诉的区别,对比下面两张图就可以很明白的看出来了。
CPU不受控的情况下开启4条线程跑立马就把CPU吃满了,而且居高不下,造成电脑卡的不行无法进行别的作业。
受控的情况:
看完图就很明白情况了吧!代码是最好交流的方式,那咱们来看一下代码:
基本任务接口:
1 public interface ICpuManagementTask2 {3 void DoWork();4 void DoWork(object a);5 }
模拟作业,实现任务接口进行作业:
1 public class ApplictionClass : ICpuManagementTask 2 { 3 4 public ApplictionClass(out ICpuManagementTask icmt) 5 { 6 icmt = this; 7 } 8 9 public void DoWork()10 {11 // Thread Code12 do13 {14 int a = 0;15 for (int i = 0; i < 100000000; i++)16 {17 a = i;18 }19 //System.Threading.Thread.Sleep(1);20 //Console.WriteLine("Task is Running " + mesg + "Id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);21 22 } while (true);23 }24 25 public void DoWork(object mesg)26 {27 // Thread Code28 do29 {30 int a = 0;31 for (int i = 0; i < 100000000; i++)32 {33 a = i;34 }35 //System.Threading.Thread.Sleep(1);36 //Console.WriteLine("Task is Running " + mesg + "Id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);37 38 } while (true);39 }40 }
模拟不受控实现方式:
1 /// <summary> 2 /// 不受控的异步执行 3 /// </summary> 4 public void Test1(ICpuManagementTask icmt) 5 { 6 System.Threading.Tasks.Task T1 = new System.Threading.Tasks.Task(new Action(icmt.DoWork)); 7 T1.Start(); 8 9 System.Threading.Tasks.Task T2 = new System.Threading.Tasks.Task(new Action(icmt.DoWork));10 T2.Start();11 12 System.Threading.Tasks.Task T3 = new System.Threading.Tasks.Task(new Action(icmt.DoWork));13 T3.Start();14 15 System.Threading.Tasks.Task T4 = new System.Threading.Tasks.Task(new Action(icmt.DoWork));16 T4.Start();17 18 }
前面这些都是很简单的,有点OO思想的一看就明白了不需要什么解释的。
既然要让任务占用硬件资源受控制那肯定是需要一些处理的,要跑的作业相当于托管在一个“容器”里面暂时叫他“自动管理CPU”吧,接下来先看一下这个“容器”
看到这个就很简单了吧!其实并没有什么神秘的,无非就是做两个事 一、创建任务 二、管理CPU使用率进行任务作业
咱们一个个看,首先看看这个枚举,他是自定义的几个模式对应当需要挂起线程时处理的事
public enum CpuManagementModelEnum { None, /// <summary> /// 中断模式 允许自动关闭一下获得任务释放CPU资源 /// </summary> BreakModel, /// <summary> /// 睡眠模式 允许将活动任务设置为睡眠状态是否CPU资源 /// </summary> SleepModel }
public virtual void CreatParallelTask(Dictionary<Thread, object> list) { Action act = new Action(() => { list.ToList().ForEach(a => a.Key.Priority = ThreadPriority.Lowest); foreach (var item in list) { item.Key.Start(item.Value); //break; } }); System.Threading.Tasks.ParallelOptions options = new System.Threading.Tasks.ParallelOptions(); options.MaxDegreeOfParallelism =list.Count ; System.Threading.Tasks.Parallel.Invoke(options, new Action[] { act }); }
创建任务也没什么比较难懂东西,就是把任务集合中的任务先用Action封装成方法 然后用System.Threading.Tasks.Parallel.Invoke 方法尽可能并行执行提供的每个操作。
后面这个控制并执行就有点复杂了,先看一下代码:
1 public virtual void CpuManagement(Dictionary<Thread, object> task, float cpuUpper, CpuManagementModelEnum cm) 2 { 3 Console.WindowWidth = 100; 4 Thread t = new Thread(() => 5 { 6 float avgCpu = 0; 7 PerformanceCounter[] counters = new PerformanceCounter[System.Environment.ProcessorCount]; 8 for (int i = 0; i < counters.Length; i++) 9 {10 counters[i] = new PerformanceCounter("Processor", "% Processor Time", i.ToString());11 }12 13 while (true)14 {15 avgCpu = 0;16 for (int i = 0; i < counters.Length; i++)17 {18 float f = counters[i].NextValue();19 avgCpu += f;20 }21 avgCpu = avgCpu / counters.Length;22 23 if (avgCpu >= cpuUpper)24 {25 Console.Write("Sleep CPU-AVG: "26 + Math.Round(avgCpu, 1).ToString().PadLeft(4, ‘ ‘) + "%"27 + " CoreCount:" + counters.Length28 + " BusyThreadCount:" + task.Count(a => a.Key.ThreadState == System.Threading.ThreadState.Running).ToString()29 + " ThreadTotalCount:" + task.Count);30 foreach (var item in task)31 {32 if (item.Key.ThreadState != System.Threading.ThreadState.Suspended)33 {34 item.Key.Suspend();35 switch (cm)36 {37 case CpuManagementModelEnum.None:38 break;39 case CpuManagementModelEnum.BreakModel:40 break;41 case CpuManagementModelEnum.SleepModel:42 System.Threading.Thread.Sleep(10);43 break;44 default:45 break;46 } 47 }48 }49 Console.Write(" Wait... \r\n");50 }51 else52 {53 Console.Write("Run CPU-AVG: " + Math.Round(avgCpu, 1).ToString().PadLeft(4, ‘ ‘) + "%"54 + " CoreCount:" + counters.Length55 + " BusyThreadCount:" + task.Count(a => a.Key.ThreadState == System.Threading.ThreadState.Running).ToString()56 + " ThreadTotalCount:" + task.Count);57 foreach (var item in task)58 {59 if (item.Key.ThreadState == System.Threading.ThreadState.Suspended)60 {61 item.Key.Resume();62 switch (cm)63 {64 case CpuManagementModelEnum.None:65 break;66 case CpuManagementModelEnum.BreakModel:67 break;68 case CpuManagementModelEnum.SleepModel:69 System.Threading.Thread.Sleep(10);70 break;71 default:72 break;73 } 74 }75 }76 Console.Write(" Buy... \r\n");77 }78 System.Threading.Thread.Sleep(250);79 }80 });81 t.Priority = ThreadPriority.Highest;82 t.Start();83 }
这里面主要就是做两个事:一、是对整体CPU资源被占用超过一个基数的时候要对现有的任务管理集合中的任务进行挂起操作;
二、是对整体CPU资源被占用小于定的基数的时候 继续已挂起的线程;
这个方法的第二个参数就是咱们想设定的CPU资源被占用到多大的基数上限开始使这时候运行的程序挂起部分线程释放资源,比如你想让CPU保持在90%左右,留有一部分空间来处理别的进程突发占用的情况或者别的情况这要视场景而定了。
在这执行任务当中用到两个已经过时的API:
System.Threading.Thread中的Suspend和Resume ,看到msdn上的有个警告
大概是说如果使用挂起的话当这个线程正好持有锁的话这个AppDomain的其他线程很可能都被堵死了,或者是在挂起一个线程在执行一个类的构造函数这个AppDomin其他线程使用这个类会受阻。很容易发生死锁现象。
可能主要就是这个原因吧!有知道比较详细的朋友可以留言告诉我,大家互相学习嘛。。。
这两个方法上倒是写了一些:
关于解决死锁问题直接使用 System.Threading.Monitor 会更好,这里主要介绍CPU多核控速问题,所以上面代码就不修改了。这个关于多线程上的五种基本问题之一的 死锁 在这就不详细讨论了。
下面咱们再回头看一下 这种受控制的情况下如何在应用程序中实现:
/// <summary> /// 受控的并行执行 /// </summary> /// <param name="icmt"></param> public void Test2(ICpuManagementTask icmt) { AutomaticCpuManagement CPU = new AutomaticCpuManagement(); Dictionary<Thread, object> task = new Dictionary<Thread, object>(); task.Add(new Thread(new ParameterizedThreadStart(icmt.DoWork)), System.DateTime.Now); task.Add(new Thread(new ParameterizedThreadStart(icmt.DoWork)), System.DateTime.Now.AddSeconds(1)); task.Add(new Thread(new ParameterizedThreadStart(icmt.DoWork)), System.DateTime.Now.AddSeconds(2)); task.Add(new Thread(new ParameterizedThreadStart(icmt.DoWork)), System.DateTime.Now.AddSeconds(3)); CPU.CreatParallelTask(task); CPU.CpuManagement(task, 90F, AutomaticCpuManagement.CpuManagementModelEnum.BreakModel); }
启动调试 的两条代码:
static void Main(string[] args) { ICpuManagementTask icmt; ApplictionClass app = new ApplictionClass(out icmt); // app.Test1(icmt); //Console.ReadKey(); app.Test2(icmt); Console.ReadKey(); }
分别调试两种方式进行观察就得到开头所看到的占用硬件资源不同的情况了。
CPU多核控速