1

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

    }
}
4

3 回答 3

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.

于 2013-02-08T07:53:47.400 回答
3

坦率地说,你做错了多线程。如果使用得当,线程是一种强大的工具,但与所有工具一样,它们并非在所有情况下都是正确的解决方案。玻璃瓶很适合装啤酒,但不适合用来敲钉子。

在一般情况下,创建更多线程不会使事情运行得更快,正如您所发现的那样,这里尤其如此。您编写的代码在您的循环中每次迭代都会排队一个新线程,并且每个线程都将分配一个堆栈。由于 .NET 世界中堆栈的默认大小是 1 MB,因此您的内存承诺不会花很长时间就会飙升。因此,超过 1 GB 也就不足为奇了。最终,您将遇到硬内存限制并被OutOfMemoryException抛出。内存只是您的设计正在迅速使您的系统缺乏资源的最明显的资源。除非您的系统资源可以随线程池成倍增长,否则您不会体验到任何性能优势。

Adil 建议Thread.Sleep在继续循环(并创建额外的线程)之前插入一个调用,让您创建的新线程有时间运行。正如我在评论中提到的,虽然这“有效”,但对我来说似乎是一个非常丑陋的黑客。但我很难提出更好的解决方案,因为真正的问题是设计。你说你必须使用线程池,但你没有说为什么会这样。

如果您绝对必须使用线程池,最好的解决方法可能是对线程池的大小设置任意限制(即它可以产生多少新线程),这通过调用SetMaxThreads方法来完成。这对我来说至少比Thread.Sleep.

注意:如果您决定采用该SetMaxThreads方法,则应注意不能将最大值设置为小于最小值。最小值的默认值是 CPU 核心数,因此如果您有双核处理器,则不能在不先降低最小值的情况下将最大值设置为 1。

最后,虽然在这种情况下它并没有真正改变答案,但值得注意的是任务管理器不是内存分析器。像依赖它一样依赖它会经常给你带来糟糕的(或至少非常误导性的)数据。

编辑:经过进一步思考,我发现问题真的不在于指数执行,而在于指数查询。允许的最大线程数可能无关紧要,因为代码仍然会比他们希望处理的速度更快地排队。所以不要介意限制大小。您可能想要使用Joachim 的解决方案,该解决方案涉及创建信号量,或者每个人都提出不使用线程池的隐含建议。

于 2013-02-08T07:41:04.787 回答
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毫秒,你根据你的应用进行调整。

于 2013-02-08T07:13:21.540 回答