Sam*_* S. 4 c# multithreading out-of-memory task
大约有1000个任务在运行,但有时我会收到任务调度程序抛出的以下内存异常.可能是什么原因以及如何避免它.
System.Threading.Tasks.TaskSchedulerException: An exception was thrown by a TaskScheduler. ---> System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown.
at System.Threading.Thread.StartInternal(IPrincipal principal, StackCrawlMark& stackMark)
at System.Threading.Thread.Start(StackCrawlMark& stackMark)
at System.Threading.Thread.Start(Object parameter)
at System.Threading.Tasks.ThreadPoolTaskScheduler.QueueTask(Task task)
at System.Threading.Tasks.Task.ScheduleAndStart(Boolean needsProtection)
--- End of inner exception stack trace ---
at System.Threading.Tasks.Task.ScheduleAndStart(Boolean needsProtection)
at System.Threading.Tasks.Task.InternalStartNew(Task creatingTask, Object action, Object state, CancellationToken cancellationToken, TaskScheduler scheduler, TaskCreationOptions options, InternalTaskOptions internalOptions, StackCrawlMark& stackMark)
at System.Threading.Tasks.TaskFactory.StartNew(Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler)
at App.StartReadSocketTask()
Run Code Online (Sandbox Code Playgroud)
您的(非x64)应用程序的最大内存空间为2GB.每个线程至少需要1 MB,通常在达到1000个线程之前可以预期OOM.
本身的Task类应该解决这个问题(通过使用ThreadPool).但是当你的任务花费太长时间(> 500毫秒)时,TP将慢慢添加线程,几分钟或更长时间后失败.
最简单的解决方案可能是查看代码中无限制创建任务的代码,并查看是否可以以与解决方案一致的方式进行限制.就像您使用Producer/Consumer Que一样,将其设置为有界队列.
否则,限制MaxThreads,但这是一个生硬的应用程序范围的工具.
小智 6
当我尝试测试并行系统的限制时,我自己也遇到了这个问题。oleksii 的评论是对的(1k 线程 ~= 1GB 的已提交内存)。重要的是要注意,此内存是保留的虚拟地址空间,而不是实际“使用”的内存量。当系统无法提交足够大的连续虚拟地址空间块来满足您的请求时,就会发生内存不足异常(在此插入“内存碎片”修辞)。如果您在 Windows 任务管理器中查看进程死亡的时间,您可能会看到只有 80-120mb 的“已用”内存。要查看保留了多少虚拟地址空间,请显示任务管理器中的“内存 - 提交大小”列。
为了保持简短,我能够通过将我的构建配置从 x86 切换到 64 位来突破 ~1k 线程限制。这将可用的虚拟地址空间量从(大约)2GB 增加到 6TB+(取决于您的操作系统版本)并且我的 OutOfMemoryException 消失了。
这是我创建的一个简单程序,它说明了这个工件,确保以 x86 运行它并观察它在 1k 和 1.5k 线程之间的某个地方消失 - 然后切换到 64 位,它应该运行到完成而不会失败。
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using System.Threading;
namespace TaskToy
{
class Program
{
static void Main( string[] args )
{
List<Task> lTasks = new List<Task>();
int lWidth = 0;
for ( int i = 0; i < 5000; i ++ )
{
lTasks.Add( new Task( (o) => {
Console.WriteLine( "B " + Interlocked.Increment( ref lWidth ) + " tid " + Thread.CurrentThread.ManagedThreadId );
Thread.Sleep( 60000 );
Console.WriteLine( "E " + Interlocked.Decrement( ref lWidth ) + " tid " + Thread.CurrentThread.ManagedThreadId );
}, null, TaskCreationOptions.LongRunning ) );
}
Parallel.For( 0, lTasks.Count, ( i ) =>
{
lTasks[i].Start();
} );
Task.WaitAll( lTasks.ToArray() );
Console.WriteLine( "DONE - press any key..." );
Console.ReadKey( true );
}
}
}
Run Code Online (Sandbox Code Playgroud)
PS 'lWidth'变量表示当前的并发级别,即一次实际运行了多少个任务。
总的来说,这是一个有趣的学术实验,但运行数千个线程可能需要“几年”才能提供有用的回报。可能建议将您旋转的线程数量限制为更实用的数量 - 可能小于“数千”的数量级。
我相信你已经遇到了一个有趣的部分ThreadPool,它决定添加更多的工作线程,因为你当前的任务正在"挨饿"等待的任务.最终这会导致应用程序内存不足.
我建议TaskCreationOptions.LongRunning在创作时添加标志.这将让它ThreadPool知道它应该考虑任务的超额认购.
作为最后的结果,您可以使用该
SetMaxThreads方法配置ThreadPool具有工作线程数上限的类,通常等于核心数(这是Environment.ProcessorCount属性)...
同一本书还建议如下:创建限制并发度的任务计划程序.
您可能同时启动了太多任务。
每个任务都可能是一个单独的线程。CLR 为每个线程分配独立的堆栈内存。我假设 x64 Windows 的典型堆栈需要 1024Kb。只需跨越线程,您就可以获得纯粹用于线程堆栈的 1GB 内存。这不包括任何堆内存或大对象堆。另外,还有其他消耗内存的进程。
| 归档时间: |
|
| 查看次数: |
9061 次 |
| 最近记录: |