Использование процессора увеличивается до 100% в бесконечном цикле в потоке - PullRequest
7 голосов
/ 16 января 2012

Я реализую веб-платформу чата в ASP.NET Web Application и использую технику, аналогичную длинному опросу.Я имею в виду, что я сохраняю каждый веб-запрос от клиента в течение определенного периода времени (тайм-аута) или до тех пор, пока не прибудет новое сообщение, а затем ответ будет отправлен клиенту.

Я сохраняю подключенных клиентов в памяти (объект словаря) и когдакогда новое сообщение отправляется клиенту, я записываю это сообщение в массив сообщений получателя клиента.Клиент должен отправить запрос, чтобы получить свои собственные сообщения, и я храню этот запрос в массиве в памяти.

Я использую асинхронный http-обработчик для прослушивания запроса клиента, я храню веб-запросы в массиве в памяти,Я использую потоки, чтобы постоянно проверять новые сообщения из памяти (в словаре, который создается для каждого клиента).

Я не использую потоки пула потоков .net для проверки новых сообщений или тайм-аутов веб-запросов. Я создаю потоки следующим образом:

System.Threading.Thread t = new Thread(new ThreadStart(QueueCometWaitRequest_WaitCallback));
t.IsBackground = false;
t.Start();

В методе QueueCometWaitRequest_WaitCallback каждого потока я нахожусь вбесконечный цикл while:

while (true)
{
...
Thread.Sleep(100);
}

В этом методе я проверяю время ожидания веб-запроса или новое сообщение для каждого веб-запроса, который также хранится в массиве в памяти.

Все былоработал хорошо, пока я не заметил, что загрузка процессора достигает 100% во времени.(через несколько минут после первого подключенного клиента) В начале первого запроса все кажется нормальным, я имею в виду использование ЦП не более 10% при возврате ответа клиенту.Но со временем даже с двумя клиентами загрузка процессора увеличивается до 100%.Кажется, загрузка процессора составляет 100% только при записи в ответ на запрос клиента.Если не осталось ни одного клиента, все возвращается к нормальному состоянию (загрузка процессора составляет около 0%), пока клиент не выполнит новый веб-запрос.

Я не знаю подробности потоков, но я с подозрениемновые темы, которые я создал и работает бесконечно.Это похоже на то, что операционная система дает им больше времени и ресурсов процессора, поскольку они работают все время, и этот Thread.Sleep (100) не работает.

Вот метод QueueCometWaitRequest_WaitCallback ():

void QueueCometWaitRequest_WaitCallback()
{
   while (true)
   {
      if (processRequest.Length == 0)
      {
          Thread.Sleep(100);
      }
      else
      {
          for (int i = 0; i < processRequest.Length; i++)
          {
               Thread.Sleep(100);

               // below I am checking for new message or request time out 
               .................
               .................

               // If new message or time out I write to response
          }
      }    
   }
}

Я надеюсь, что смогу объяснить ситуацию, и я также открыт для любых предложений (например, для реализации по-другому)

Если вы можете помочь мне с этой проблемой, я буду благодаренСпасибо

Ответы [ 2 ]

9 голосов
/ 16 января 2012

Точно так же, как общий комментарий передового опыта, а не прямой ответ - не рекомендуется писать Thread.Sleep (100) в вашей ветке получателя сообщений.Лучшим способом было бы использовать Thread.Join, как упоминалось ранее, или ручные дескрипторы ожиданияReReventEvent.Например, вы можете написать код, подобный следующему:

private ManualResetEvent waitHandle;
private object syncRoot = new object();
private bool isRunning = false;

void CreateThread()
{
    this.waitHandle = new ManualResetEvent(false);

    isRunning = true; // Set to false to kill the thread
    System.Threading.Thread t = new Thread(new ThreadStart(QueueCometWaitRequest_WaitCallback));         
    t.IsBackground = false; 
    t.Start();
}

void PushData()
{
    // On incoming data, push data into the processRequest queue and set the waithandle
    lock(syncRoot)
    {
        processRequest.Add(/* ... your data object to process. Assumes this is a queue */);
        waitHandle.Set(); // Signal to the thread there is data to process
    }
}

void QueueCometWaitRequest_WaitCallback() 
{    
    while (isRunning)    
    {       
        // Waits here using 0% CPU until the waitHandle.Set is called above
        this.waitHandle.WaitOne();

        // Ensures no-one sets waithandle while data is being processed and
        // subsequently reset
        lock(syncRoot)
        {
            for (int i = 0; i < processRequest.Length; i++)           
            {                        
                // Process the message. 
                // What's the type of processRequest? Im assuming a queue or something     
            }       

            // Reset the Waithandle for the next requestto process
            this.waitHandle.Reset();
        }
    }        
} 

Это гарантирует, что ваш поток использует 0% ЦП во время ожидания и потребляет ЦП только тогда, когда есть работа, которую нужно выполнить.

Не задумывались ли вы о стороннем решении асинхронного двунаправленного обмена сообщениями?Я с большим успехом использовал RabbitMQ (AMQP) в приложениях .NET для обработки сообщений с высокой пропускной способностью.API для RabbitMQ означает, что при получении сообщения вы получаете событие, которое затем может быть обработано в фоновом потоке.

С уважением,

0 голосов
/ 03 октября 2016

Я храню подключенных клиентов в памяти (объект словаря)

Объекты словаря не являются поточно-ориентированными, если используются статически.Если вы используете его как статический член, вам нужно создать оператор Lock.

Вот пример, взятый из класса Log4F LoggerFactory ... Обратите внимание, что TypeToLoggerMap является объектом словаря, а когда на него ссылается vai, метод GetLoggerиспользуется оператор Lock.

public static class LoggerFactory
{
    public static ILogger GetLogger(Ninject.Activation.IContext context)
    {
        return GetLogger(context.Request.Target == null ? typeof(ILogger) : context.Request.Target.Member.DeclaringType);
    }

    private static readonly Dictionary<Type, ILogger> TypeToLoggerMap = new Dictionary<Type, ILogger>();

    private static ILogger GetLogger(Type type)
    {
        lock (TypeToLoggerMap)
        {
            if (TypeToLoggerMap.ContainsKey(type))
                return TypeToLoggerMap[type];

            ILogger logger = new Logger(type);
            TypeToLoggerMap.Add(type, logger);

            return logger;
        }
    }
}

Проверьте эту статью - здесь я обнаружил вышеупомянутую информацию об объектах Dictionary.

https://www.toptal.com/dot-net/hunting-high-cpu-usage-in-dot-net

Какпримечание, вы рассматривали возможность использования SignalR для вашего проекта?

...