使用多线程可能导致过多的内存使用

时间:2013-09-23 08:10:14

标签: c# multithreading service

我有一个Windows服务项目,它将消息记录到数据库(或其他地方)。这些消息的频率最高可达每秒十个。由于发送和处理消息不应该延迟服务的主进程,因此我开始一个新线程来处理每个消息。这意味着如果主进程需要发送100条日志消息,则会启动100个处理每条消息的线程。我了解到,当一个线程完成后,它将被清理,所以我不必处理它。只要我在线程中处理所有使用过的对象,一切都应该正常工作。

该服务可能会进入导致关闭服务的异常。在服务关闭之前,它应该等待所有记录消息的线程。为实现此目的,每次启动线程时,它都会将线程添加到列表中。调用wait-for-threads方法时,将检查列表中的所有线程是否仍处于活动状态,如果是,则使用join等待它。

代码:

创建线程:

/// <summary>
    /// Creates a new thread and sends the message
    /// </summary>
    /// <param name="logMessage"></param>
    private static void ThreadSend(IMessage logMessage)
    {
        ParameterizedThreadStart threadStart = new ParameterizedThreadStart(MessageHandler.HandleMessage);
        Thread messageThread = new Thread(threadStart);
        messageThread.Name = "LogMessageThread";            
        messageThread.Start(logMessage);
        threads.Add(messageThread);
    }

等待线程结束:

    /// <summary>
    /// Waits for threads that are still being processed
    /// </summary>
    public static void WaitForThreads()
    {
        int i = 0;
        foreach (Thread thread in threads)
        {
            i++;
            if (thread.IsAlive)
            {
                Debug.Print("waiting for {0} - {1} to end...", thread.Name, i);
                thread.Join();
            }
        }
    }

现在我主要担心的是,如果这个服务运行一个月,它仍将拥有列表中的所有线程(数百万)(大多数已经死亡)。这会吃掉记忆,我不知道多少。这对我来说似乎不是一个好习惯,我想清理已完成的线程,但我无法找到如何做到这一点。有没有人有这个好的或最好的做法?

5 个答案:

答案 0 :(得分:2)

如果它们已经死了,请从列表中删除它们吗?

/// <summary>
/// Waits for threads that are still being processed
/// </summary>
public static void WaitForThreads()
{
    List<Thread> toRemove = new List<int>();

    int i = 0;
    foreach (Thread thread in threads)
    {
        i++;
        if (thread.IsAlive)
        {
            Debug.Print("waiting for {0} - {1} to end...", thread.Name, i);
            thread.Join();
        }
        else
        {
            toRemove.Add(thread);
        }
    }
    threads.RemoveAll(x => toRemove.Contains(x));
}

查看Task Parallelism

答案 1 :(得分:2)

首先:每条日志消息创建一个线程是个好主意。使用ThreadPool或创建有限数量的工作线程来处理来自公共队列(生产者/消费者)的日志项。

第二:当然你还需要从列表中删除线程引用!当线程方法结束时,它可以自行删除,或者你甚至可以定期执行它。例如,让计时器每半小时运行一次,检查列表中的死线程并将其删除。

答案 2 :(得分:1)

如果你在这些线程中所做的只是记录,你应该有一个记录线程和主线程放置消息的共享队列。然后,日志记录线程可以读取队列并记录日志。使用BlockingCollection非常容易。

在服务的主线程中创建队列:

BlockingCollection<IMessage> LogMessageQueue = new BlockingCollection<IMessage>();

您的服务的主线程创建一个Logger(见下文)实例,该实例启动一个线程来处理日志消息。主线程将项添加到LogMessageQueue。记录器线程从队列中读取它们。当主线程想要关闭时,它会调用LogMessageQueue.CompleteAdding。记录器将清空队列并退出。

主线程看起来像这样:

// start the logger
Logger _loggingThread = new Logger(LogMessageQueue);

// to log a message:
LogMessageQueue.Add(logMessage);

// when the program needs to shut down:
LogMessageQueue.CompleteAdding();

记录器类:

class Logger
{
    BlockingCollection<IMessage> _queue;
    Thread _loggingThread;

    public Logger(BlockingCollection<IMessage> queue)
    {
        _queue = queue;
        _loggingThread = new Thread(LoggingThreadProc);
    }

    private void LoggingThreadProc(object state)
    {
        IMessage msg;
        while (_queue.TryTake(out msg, TimeSpan.Infinite))
        {
            // log the item
        }
    }
}

这样你只有一个额外的线程,保证消息按照它们发送的顺序进行处理(不适用于你当前的方法),你不必担心跟踪线程关闭等

更新

如果您的某些日志消息需要一些时间来处理(例如,您所描述的电子邮件),则可以异步处理它们。例如:

while (_queue.TryTake(out msg, TimeSpan.Infinite))
{
    if (msg.Type == Email)
    {
        // start asynchronous task to send email
    }
    else
    {
        // write to log file
    }
}

这样,只有那些可能需要大量时间的消息才会异步运行。如果需要,您还可以在其中为电子邮件设置辅助队列。这样你就不会陷入一堆电子邮件线程。相反,你把它限制在一两个,或者可能是极少数。

请注意,如果需要,您还可以拥有多个Logger个实例,所有实例都从同一个消息队列中读取。只需确保他们每个人都写入不同的日志文件。队列本身将支持多个消费者。

答案 3 :(得分:0)

我认为一般来说,解决问题的方法可能不是最佳做法。 我的意思是,你只想在数据库中存储1000条消息,而不是创建1000个线程吗?似乎你想要异步地做这件事。

但是为每条消息创建一个线程并不是一个好主意,实际上并没有解决这个问题......

相反,我会尝试实现消息队列之类的东西。您可以拥有多个队列,每个队列都有自己的线程。如果消息进入,您将它们发送到其中一个队列(交替)...

队列要么等待一定数量的消息,要么总是等待一定的时间(例如1秒,取决于在数据库中存储例如100条消息所需的时间),直到它尝试存储排队的消息为止在数据库中。 这样你实际上应该总是有一个恒定数量的线程,你不应该看到任何性能问题......

此外,它还可以让您批量插入数据,而不仅仅是逐个数据库连接等的开销......

原因是,如果您的数据库速度较慢,则任务可以存储消息,则会有越来越多的消息排队......但对于您当前的解决方案来说也是如此。

答案 4 :(得分:0)

由于我的解决方案有多个答案和评论,我将在此处发布完整的代码。

我使用threadpool来管理来自这个page的线程和代码,用于wating函数。

创建线程:

private static void ThreadSend(IMessage logMessage)
        {    
            ThreadPool.QueueUserWorkItem(MessageHandler.HandleMessage, logMessage);
        }

等待线程完成:

public static bool WaitForThreads(int maxWaitingTime)
        {
            int maxThreads = 0;
            int placeHolder = 0;
            int availableThreads = 0;

            while (maxWaitingTime > 0)
            {
                System.Threading.ThreadPool.GetMaxThreads(out maxThreads, out placeHolder);
                System.Threading.ThreadPool.GetAvailableThreads(out availableThreads, out placeHolder);

                //Stop if all threads are available
                if (availableThreads == maxThreads)
                {
                    return true;
                }

                System.Threading.Thread.Sleep(TimeSpan.FromMilliseconds(1000));
                --maxWaitingTime;            
            }

            return false;
        }

您可以选择在这些方法之外的某处添加它以限制池中的线程数量。

System.Threading.ThreadPool.SetMaxThreads(MaxWorkerThreads, MaxCompletionPortThreads);