我已经制作了一个简单的控制台应用程序来打印素数。我正在使用ThreadPool来检查数字是否为素数。
在任务管理器中,此程序开始占用太多内存(几秒钟内1 GB) 如果我还要使用ThreadPool,我该如何改进呢?
这是我写的代码
class Program
{
static void Main(string[] args)
{
Console.WriteLine(2);
Console.WriteLine(3);
Console.WriteLine(5);
Console.WriteLine(7);
Console.WriteLine(11);
Console.WriteLine(13);
Console.WriteLine(17);
for (long i = 19; i < Int64.MaxValue; i = i+2)
{
if(i % 3 == 0 || i % 5 == 0 || i % 7 == 0 || i % 11 == 0 || i % 13 == 0 || i % 17 == 0 )
continue;
ThreadPool.QueueUserWorkItem(CheckForPrime, i);
}
Console.Read();
}
private static void CheckForPrime(object i)
{
var i1 = i as long?;
var val = Math.Sqrt(i1.Value);
for (long j = 19; j <= val; j = j + 2)
{
if (i1 % j == 0) return;
}
Console.WriteLine(i1);
}
}
答案 0 :(得分:4)
修复代码的最简单方法,只需使用信号量限制工作队列;
class Program
{
// Max 100 items in queue
private static readonly Semaphore WorkLimiter = new Semaphore(100, 100);
static void Main(string[] args)
{
Console.WriteLine(2);
Console.WriteLine(3);
Console.WriteLine(5);
Console.WriteLine(7);
Console.WriteLine(11);
Console.WriteLine(13);
Console.WriteLine(17);
for (long i = 19; i < Int64.MaxValue; i = i + 2)
{
if (i % 3 == 0 || i % 5 == 0 || i % 7 == 0 || i % 11 == 0 || i % 13 == 0 || i % 17 == 0)
continue;
// Get one of the 100 "allowances" to add to the queue.
WorkLimiter.WaitOne();
ThreadPool.QueueUserWorkItem(CheckForPrime, i);
}
Console.Read();
}
private static void CheckForPrime(object i)
{
var i1 = i as long?;
try
{
var val = Math.Sqrt(i1.Value);
for (long j = 19; j <= val; j = j + 2)
{
if (i1%j == 0) return;
}
Console.WriteLine(i1);
}
finally
{
// Allow another add to the queue
WorkLimiter.Release();
}
}
}
这将允许您始终保持队列已满(队列中有100个项目),而不会过度填充或添加Sleep
。
答案 1 :(得分:2)
坦率地说,你做错多线程。如果使用正确,线程是一个强大的工具,但与所有工具一样,它们不是每个情况下的正确解决方案。一个玻璃瓶适合拿着啤酒,但不能用于锤击指甲。
在一般情况下,创建更多线程并不会让事情运行得更快,尤其如你所发现的那样。您编写的代码在循环中每次迭代时排队一个新线程,并且每个线程将分配一个堆栈。由于.NET世界中堆栈的默认大小为1 MB,因此您的内存承诺不会花费很长时间。因此,您超过1 GB也就不足为奇了。最终,您将遇到硬内存限制,并向您发出OutOfMemoryException
。内存只是您的设计快速使系统匮乏的最明显的资源。除非您的系统资源可以与您的线程池成指数级增长,否则您将不会遇到任何性能优势。
Adil suggests插入对Thread.Sleep
的调用,以便在继续循环(并创建其他线程)之前为您创建的新线程提供运行时间。正如我在评论中提到的,尽管这“有效”,但对我来说这似乎是一个非常难看的黑客。但是我很难提出更好的解决方案,因为真正的问题是设计。你说你必须使用线程池,但你没有说明为什么会这样。
如果您绝对 使用线程池,那么最好的解决方法可能是对线程池的大小设置一个任意限制(即,它可以生成多少个新线程),通过调用SetMaxThreads
method来完成。这对我来说至少比Thread.Sleep
少得多。
注意:如果您决定采用SetMaxThreads
方法,则应注意不能将最大值设置为小于最小值。最小值的默认值是CPU核心数,因此如果您有双核处理器,则在不先降低最小值的情况下,不能将最大值设置为1.
最后,虽然在这种情况下并没有真正改变答案,但值得注意的是,任务管理器不是内存分析器。依赖它就好像是一个经常会让你感觉不好(或者至少是非常误导性的)数据。
编辑经过进一步思考后,我发现问题确实不在于指数执行,而在于指数查询。允许的最大线程数可能是无关紧要的,因为代码仍然会比他们希望处理的速度更快地排队。所以不要介意限制尺寸。您可能希望使用Joachim's solution来创建信号量,或隐含的建议,即每个人都不使用线程池。
答案 2 :(得分:1)
您正在循环中创建threads
而没有任何中断。您应该在创建线程的过程中给予一些休息,以便某些线程在ThreadPool
中创建更多线程之前完成其执行。您可以使用System.Threading.Thread.Sleep。
for (long i = 19; i < Int64.MaxValue; i = i+2)
{
if(i % 3 == 0 || i % 5 == 0 || i % 7 == 0 || i % 11 == 0 || i % 13 == 0 || i % 17 == 0 )
continue;
ThreadPool.QueueUserWorkItem(CheckForPrime, i);
System.Threading.Thread.Sleep(100);
}
您应该知道在哪里使用线程,它们将是有益的,您需要多少线程以及线程对应用程序性能的影响。这取决于应用程序将挂起当前线程的时间。我只给了100毫秒,你可以根据你的应用进行调整。