Почему эта функция использует 100% CPU? - PullRequest
0 голосов
/ 21 августа 2011
private static string GetProxy()
{
    var rnd = new Random();
    if (Settings.Globals.UsedProxies.Count >= 100)
    {
        Settings.Globals.UsedProxies.Clear();
    }

Start:
    var inx = rnd.Next(0, Settings.Globals.Proxies.Count);
    var theProx = Settings.Globals.Proxies[inx];
    foreach (var item in Settings.Globals.UsedProxies)
    {
        if (item == theProx)
            goto Start;
    }
    Settings.Globals.UsedProxies.Add(theProx);
    return theProx;
}

Я вызываю этот код из пула из 5 потоков на случайных интервалах от 10 до 30 секунд. Это использует 100% CPU и очень сильно отстает от системы. Если я закомментирую свой звонок в GetProxy, приложение использует только 7% CPU. Есть идеи?

Идея в том, что у меня есть список с 1000 прокси. После использования прокси я хочу добавить его в список используемых прокси и никогда не использовать прокси, который уже использовался.

Ответы [ 4 ]

3 голосов
/ 21 августа 2011

Ваш забавный цикл goto гарантированно будет работать вечно.
Ваш код выбирает случайный элемент из списка, циклически повторяется до тех пор, пока не найдет этот элемент, и не начнется снова.

Как только все прокси используются, ваш код будет зацикливаться вечно, потому что он не может найти больше прокси для добавления.

Кроме того, List<T> не является поточно-безопасно, так что ваш код может быть непредсказуемым образом.

1 голос
/ 21 августа 2011

Строго говоря, это не ответ на вопрос OP (почему эта функция использует 100% ЦП), однако у OP есть проблемы с состоянием гонки, из-за которых списки могут вести себя беспорядочно. Поэтому я подумал, что смогу продемонстрировать один из способов справиться с этим

Насколько я понимаю, код выделяет случайную строку прокси из списка прокси. Код проверяет, свободен ли он, если нет, он пытается выбрать другую строку прокси.

Одна из проблем с кодом заключается в том, что заявлено, что этот код вызывается одновременно, но к коду не безопасно обращаться одновременно.

Одним из способов справиться с этим является введение класса ProxyPool, который безопасно обрабатывает параллельный доступ.

Ниже приведен код, который может быть использован в качестве отправной точки для создания класса ProxyPool:

namespace SO_ProxyPool
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Threading.Tasks;

    sealed class ProxyPool
    {
        readonly object m_lock = new object ();
        readonly Random m_random = new Random ();
        readonly HashSet<string> m_usedProxies = new HashSet<string>();
        readonly HashSet<string> m_freeProxies = new HashSet<string>();
        volatile int m_minSize;

        public ProxyPool (IEnumerable<string> availableProxies)
        {
            m_freeProxies = new HashSet<string> (availableProxies);
            m_minSize = m_freeProxies.Count;
        }

        /// <summary>
        /// Reserves a proxy, returns null if no proxy is available
        /// </summary>
        /// <returns>The reserver proxy or null if no proxy is available</returns>
        public string ReserveProxy ()
        {
            lock (m_lock)
            {
                if (m_freeProxies.Count == 0)
                {
                    return null;
                }

                var index = m_random.Next (0, m_freeProxies.Count);

                var proxy = m_freeProxies.ElementAt (index);

                var removeSuccessful = m_freeProxies.Remove (proxy);
                var addSuccessful = m_usedProxies.Add (proxy);
                Debug.Assert (removeSuccessful);
                Debug.Assert (addSuccessful);

                m_minSize = Math.Min (m_minSize, m_freeProxies.Count);

                return proxy;
            }
        }

        /// <summary>
        /// Returns the minimum size of the pool so far
        /// </summary>
        public int MinSize
        {
            get
            {
                return m_minSize;
            }
        }

        /// <summary>
        /// Frees a reserved proxy
        /// </summary>
        /// <param name="proxy">The proxy to free</param>
        public void FreeProxy (string proxy)
        {
            if (proxy == null)
            {
                return;
            }

            lock (m_lock)
            {
                var removeSuccessful = m_usedProxies.Remove (proxy);
                if (removeSuccessful)
                {
                    var addSuccessful = m_freeProxies.Add (proxy);
                    Debug.Assert (addSuccessful);
                }

            }
        }
    }

    class Program
    {
        static readonly ProxyPool s_proxyPool = new ProxyPool (
            new[] { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", }
            );

        static string GetProxy ()
        {
            return s_proxyPool.ReserveProxy ();
        }

        static void FreeProxy (string proxy)
        {
            s_proxyPool.FreeProxy (proxy);
        }

        static void SimplisticTestCase ()
        {
            var proxy = GetProxy ();
            // Do something relevant...
            if (proxy != null)
            {
                FreeProxy (proxy);
            }
        }

        static void Main (string[] args)
        {
            var then = DateTime.Now;

            const int count = 10000000;
            Parallel.For (0, count, idx => SimplisticTestCase ());

            var diff = DateTime.Now - then;

            Console.WriteLine (
                "#{0} executions took {1:0.00}secs, pool min size {2}", 
                count,
                diff.TotalSeconds,
                s_proxyPool.MinSize
                );
        }
    }
}
1 голос
/ 21 августа 2011

Чтобы ответить на реальный вопрос, он использует 100% ЦП (на одноядерном компьютере, я полагаю), потому что все достаточно мало, чтобы поместиться в памяти, и мы просто перебираем и делаем некоторые проверки. Это очень сильно загружает процессор.

Чтобы создать список неиспользуемых прокси, вы можете сделать следующее:

HashSet unused = new HashSet(Settings.Globals.Proxies);
<s>List unused = all.ExceptWith(Settings.Globals.UsedProxies);</s>
unused.ExceptWith(Settings.Globals.UsedProxies);

, затем выберите случайный прокси из набора unused, используя свойство unused.Count и unused.GetEnumerator().

0 голосов
/ 21 августа 2011

Попробуйте это (предполагая, что все прокси в UsedProxies можно найти в Proxies):

List<string> unusedProxies = new List<string>(Settings.Globals.Proxies);
foreach (string proxy in Settings.Globals.UsedProxies)
{
    unusedProxies.Remove(proxy);
}

int inx = rnd.Next(0, unusedProxies.Count);
string proxy = unusedProxies[inx];
Settings.Globals.UsedProxies.Add(proxy);
return proxy;

Это должно быть быстрее, чем ваша версия, поскольку все неиспользуемые прокси находятся в своем собственном отдельном списке,Затем вы можете использовать rnd.Next для получения случайного прокси, и этот прокси гарантированно не будет использоваться.

...