长时间IO极限耗尽硬盘时应自定义队列依次执行
当需要对磁盘做长时间高速IO时,为了避免IO争用造成大量时间用于寻道而导致吞吐量降低,以及长时间IO造成错误数超限导致磁盘进入降速模式。在这种极限耗尽磁盘性能的情况下,应当把磁盘当做只能做单个任务的串行设备来用,对所有IO自行控制,排队依次执行,不能过分相信操作系统能把该问题解决好。
1 using System; 2 using System.Collections.Concurrent; 3 using System.Collections.Generic; 4 using System.Diagnostics; 5 using System.IO; 6 using System.Threading; 7 using Timer = System.Timers.Timer; 8 9 namespace FileMultiWriter 10 { 11 public enum DiskLargeIOBackgroudServiceAddRetrunCode 12 { 13 Succeed, 14 Fail, 15 FilePathNotInDisk, 16 ServiceStopping 17 } 18 19 public enum IOType 20 { 21 Read, 22 Write 23 } 24 25 public class DiskLargeIOBackgroudService 26 { 27 private const int TimeSliceReserveForOtherIO = 1000; 28 private volatile bool isStopping = false; 29 private volatile bool isRunning = false; 30 private object ioLock = new object(); 31 private ConcurrentQueue<Tuple<IOType, string, byte[], Action<object>>> queue = new ConcurrentQueue<Tuple<IOType, string, byte[], Action<object>>>(); 32 private Timer timer; 33 private Dictionary<char, bool> diskLogicDrives = new Dictionary<char, bool>(); 34 35 public DiskLargeIOBackgroudService(char[] logicDriveLabels, double checkIterval) 36 { 37 foreach (var logicDriveLabel in logicDriveLabels) 38 { 39 diskLogicDrives[logicDriveLabel] = true; 40 } 41 timer = new Timer(checkIterval); 42 timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed); 43 timer.Start(); 44 45 SetIOPriorityToIdle(); 46 } 47 48 #region public method 49 50 public DiskLargeIOBackgroudServiceAddRetrunCode Enqueue(IOType ioType, string path, byte[] data, Action<object> callback) 51 { 52 if (isStopping) return DiskLargeIOBackgroudServiceAddRetrunCode.ServiceStopping; 53 if (!CheckPath(path)) return DiskLargeIOBackgroudServiceAddRetrunCode.FilePathNotInDisk; 54 55 Tuple<IOType, string, byte[], Action<object>> item = new Tuple<IOType, string, byte[], Action<object>>(ioType, path, data, callback); 56 queue.Enqueue(item); 57 return DiskLargeIOBackgroudServiceAddRetrunCode.Succeed; 58 } 59 60 public void Stop() 61 { 62 isStopping = true; 63 while (!queue.IsEmpty) 64 { 65 Thread.Sleep(1000); 66 } 67 timer.Stop(); 68 timer.Elapsed -= timer_Elapsed; 69 70 } 71 72 #endregion 73 74 #region private method 75 76 private void SetIOPriorityToIdle() 77 { 78 Process process = Process.GetCurrentProcess(); 79 IntPtr hProcess = process.Handle; 80 81 ApiHelper.SetIOPriority(hProcess, IoPriority.Idle); 82 } 83 84 private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) 85 { 86 CheckQueue(); 87 } 88 89 private bool CheckPath(string path) 90 { 91 char drive = path.ToLower()[0]; 92 if (diskLogicDrives.ContainsKey(drive)) return true; 93 return false; 94 } 95 96 private void CheckQueue() 97 { 98 if (isRunning) return; 99 100 if (queue.IsEmpty) return; 101 102 if (TryAquireLock()) 103 { 104 SyncRun(); 105 } 106 } 107 108 private bool TryAquireLock() 109 { 110 bool result = false; 111 lock (ioLock) 112 { 113 if (isRunning == false) 114 { 115 isRunning = true; 116 result = true; 117 } 118 } 119 return result; 120 } 121 122 private void ReleaseLock() 123 { 124 lock (ioLock) 125 { 126 isRunning = false; 127 } 128 } 129 130 private void SyncRun() 131 { 132 try 133 { 134 DiskOperation(); 135 } 136 catch (Exception ex) 137 { 138 throw ex; 139 } 140 finally 141 { 142 ReleaseLock(); 143 } 144 } 145 146 private void DiskOperation() 147 { 148 Tuple<IOType, string, byte[], Action<object>> item; 149 while (!queue.IsEmpty) 150 { 151 while (queue.TryDequeue(out item)) 152 { 153 if (item.Item1 == IOType.Write) 154 { 155 Write(item); 156 } 157 else 158 { 159 Read(item); 160 } 161 Thread.Sleep(TimeSliceReserveForOtherIO); 162 } 163 } 164 } 165 166 private void Write(Tuple<IOType, string, byte[], Action<object>> item) 167 { 168 using (FileStream fs = new FileStream(item.Item2, FileMode.Create, FileAccess.Write)) 169 { 170 fs.Write(item.Item3, 0, item.Item3.Length); 171 fs.Close(); 172 } 173 Action<object> action = item.Item4; 174 action.BeginInvoke(item.Item2, null, null); 175 } 176 177 private void Read(Tuple<IOType, string, byte[], Action<object>> item) 178 { 179 byte[] buf; 180 using (FileStream fs = new FileStream(item.Item2, FileMode.Open, FileAccess.Read)) 181 { 182 buf = new byte[fs.Length]; 183 int offset = 0; 184 int segment = 1024*1024; 185 while (offset < buf.Length) 186 { 187 int readNum = fs.Read(buf, offset, segment); 188 offset += readNum; 189 } 190 fs.Close(); 191 } 192 Action<object> action = item.Item4; 193 action.BeginInvoke(buf, null, null); 194 } 195 196 #endregion 197 } 198 }
由于硬盘读写时默认是全速运行,为了避免长时间高速IO造成系统失去响应,应设置IO优先级为Idle。
1 using System; 2 using System.Runtime.InteropServices; 3 4 namespace FileMultiWriter 5 { 6 public class ApiHelper 7 { 8 public static int SetIOPriority(IntPtr hProcess, IoPriority newPriority) 9 { 10 int sizeOfInt = 4; 11 byte[] src = new byte[sizeOfInt]; 12 src[0] = (byte)newPriority; 13 IntPtr priorityPointer = Marshal.AllocHGlobal(new IntPtr(sizeOfInt)); 14 Marshal.Copy(src, 0, priorityPointer, sizeOfInt); 15 int result = ApiHelper.NtSetInformationProcess(hProcess, PROCESS_INFORMATION_CLASS.ProcessIoPriority, priorityPointer, (uint)sizeOfInt); 16 Marshal.FreeHGlobal(priorityPointer); 17 return result; 18 } 19 20 [DllImport("kernel32.dll")] 21 public static extern bool CreateProcess(string lpApplicationName, string lpCommandLine, ref SECURITY_ATTRIBUTES lpProcessAttributes, ref SECURITY_ATTRIBUTES lpThreadAttributes, bool bInheritHandles, uint dwCreationFlags, IntPtr lpEnvironment, string lpCurrentDirectory, [In] ref STARTUPINFO lpStartupInfo, out PROCESS_INFORMATION lpProcessInformation); 22 23 [DllImport("ntdll.dll", SetLastError = true)] 24 public static extern int NtQueryInformationProcess(IntPtr processHandle, PROCESS_INFORMATION_CLASS processInformationClass, ref IntPtr processInformation, uint processInformationLength, out uint returnLength); 25 26 [DllImport("ntdll.dll", SetLastError = true)] 27 public static extern int NtSetInformationProcess(IntPtr processHandle, PROCESS_INFORMATION_CLASS processInformationClass, IntPtr processInformation, uint processInformationLength); 28 29 [DllImport("KERNEL32.DLL")] 30 public static extern IntPtr OpenProcess(PROCESS_RIGHTS dwDesiredAccess, bool bInheritHandle, int dwProcessId); 31 32 [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)] 33 public static extern bool SetPriorityClass(IntPtr handle, uint priorityClass); 34 } 35 36 [StructLayout(LayoutKind.Sequential)] 37 public class SECURITY_ATTRIBUTES 38 { 39 public int nLength; 40 public byte[] lpSecurityDescriptor; 41 public int bInheritHandle; 42 } 43 44 public enum PROCESS_INFORMATION_CLASS 45 { 46 ProcessBasicInformation, 47 ProcessQuotaLimits, 48 ProcessIoCounters, 49 ProcessVmCounters, 50 ProcessTimes, 51 ProcessBasePriority, 52 ProcessRaisePriority, 53 ProcessDebugPort, 54 ProcessExceptionPort, 55 ProcessAccessToken, 56 ProcessLdtInformation, 57 ProcessLdtSize, 58 ProcessDefaultHardErrorMode, 59 ProcessIoPortHandlers, 60 ProcessPooledUsageAndLimits, 61 ProcessWorkingSetWatch, 62 ProcessUserModeIOPL, 63 ProcessEnableAlignmentFaultFixup, 64 ProcessPriorityClass, 65 ProcessWx86Information, 66 ProcessHandleCount, 67 ProcessAffinityMask, 68 ProcessPriorityBoost, 69 ProcessDeviceMap, 70 ProcessSessionInformation, 71 ProcessForegroundInformation, 72 ProcessWow64Information, 73 ProcessImageFileName, 74 ProcessLUIDDeviceMapsEnabled, 75 ProcessBreakOnTermination, 76 ProcessDebugObjectHandle, 77 ProcessDebugFlags, 78 ProcessHandleTracing, 79 ProcessIoPriority, 80 ProcessExecuteFlags, 81 ProcessResourceManagement, 82 ProcessCookie, 83 ProcessImageInformation, 84 ProcessCycleTime, 85 ProcessPagePriority, 86 ProcessInstrumentationCallback, 87 ProcessThreadStackAllocation, 88 ProcessWorkingSetWatchEx, 89 ProcessImageFileNameWin32, 90 ProcessImageFileMapping, 91 ProcessAffinityUpdateMode, 92 ProcessMemoryAllocationMode, 93 MaxProcessInfoClass 94 } 95 96 97 public enum PROCESS_RIGHTS : uint 98 { 99 PROCESS_ALL_ACCESS = 0x1fffff, 100 PROCESS_CREATE_PROCESS = 0x80, 101 PROCESS_CREATE_THREAD = 2, 102 PROCESS_DUP_HANDLE = 0x40, 103 PROCESS_QUERY_INFORMATION = 0x400, 104 PROCESS_QUERY_LIMITED_INFORMATION = 0x1000, 105 PROCESS_SET_INFORMATION = 0x200, 106 PROCESS_SET_QUOTA = 0x100, 107 PROCESS_SET_SESSIONID = 4, 108 PROCESS_SUSPEND_RESUME = 0x800, 109 PROCESS_TERMINATE = 1, 110 PROCESS_VM_OPERATION = 8, 111 PROCESS_VM_READ = 0x10, 112 PROCESS_VM_WRITE = 0x20 113 } 114 115 [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 116 public struct STARTUPINFO 117 { 118 public int cb; 119 public string lpReserved; 120 public string lpDesktop; 121 public string lpTitle; 122 public int dwX; 123 public int dwY; 124 public int dwXSize; 125 public int dwYSize; 126 public int dwXCountChars; 127 public int dwYCountChars; 128 public int dwFillAttribute; 129 public int dwFlags; 130 public short wShowWindow; 131 public short cbReserved2; 132 public IntPtr lpReserved2; 133 public IntPtr hStdInput; 134 public IntPtr hStdOutput; 135 public IntPtr hStdError; 136 } 137 138 [StructLayout(LayoutKind.Sequential)] 139 public struct PROCESS_INFORMATION 140 { 141 public IntPtr hProcess; 142 public IntPtr hThread; 143 public int dwProcessId; 144 public int dwThreadId; 145 } 146 147 148 [StructLayout(LayoutKind.Sequential)] 149 public struct PROCESS_BASIC_INFORMATION 150 { 151 public int ExitStatus; 152 public int PebBaseAddress; 153 public int AffinityMask; 154 public int BasePriority; 155 public int UniqueProcessId; 156 public int InheritedFromUniqueProcessId; 157 public int Size 158 { 159 get 160 { 161 return 0x18; 162 } 163 } 164 } 165 166 //only 0 1 2 can be set in user mode, set to idle can prevent system no response 167 public enum IoPriority 168 { 169 Idle = 0, 170 Low = 1, 171 Normal = 2 172 } 173 }
测试代码:
1 private static void WriteTest3() 2 { 3 DiskLargeIOBackgroudService service = new DiskLargeIOBackgroudService(new char[2] { 'c', 'd' }, 1000); 4 string path = @"D:\down\tempData\"; 5 6 string teststring = "abcdefghijABCDEFGHIJ0123456789"; 7 for (int i = 0; i < 10; i++) 8 { 9 byte[] mockArray = new byte[1024 * 1024 * 512]; 10 for (int j = 0; j < 1024 * 1024 * 500; j++) 11 { 12 mockArray[j] = (byte)teststring[i]; 13 } 14 string filename = GenPath(path + i.ToString()); 15 service.Enqueue(IOType.Write, filename, mockArray, (x) => Console.WriteLine(x)); 16 service.Enqueue(IOType.Read, filename, null, x => PrintFile(x)); 17 } 18 19 Thread.Sleep(2000); 20 service.Stop(); 21 } 22 23 private static void PrintFile(object file) 24 { 25 byte[] array = (byte[]) file; 26 int offset = 0; 27 while (offset < array.Length) 28 { 29 if(offset%(1024*1024)==0) 30 Console.Write((char)array[offset]); 31 offset++; 32 } 33 GC.Collect(); 34 }