ThreadPool在几秒钟内就会占用过多的内存

时间:2013-02-08 07:08:56

标签: c# threadpool

我已经制作了一个简单的控制台应用程序来打印素数。我正在使用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);

    }
}

3 个答案:

答案 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毫秒,你可以根据你的应用进行调整。