Распараллеливание задачи с использованием .AsParallel (). ForAll или Parallel.ForEach проблема производительности - PullRequest
2 голосов
/ 07 декабря 2011

У меня есть список веб-сайтов и список прокси-серверов.

У меня есть это действие

Action<string> action = (string url) =>
{
    var proxy = ProxyHandler.GetProxy();
    HtmlDocument html = null;
    while (html == null)
    {
        try
        {

            html = htmlDocumentLoader.LoadDocument(url, proxy.Address);

            // Various db manipulation code

            ProxyHandler.ReleaseProxy(proxy);
        }
        catch (Exception exc)
        {
            Console.WriteLine("{0} proxies remain", ProxyHandler.ListSize());

            // Various db manipulation code

            proxy = ProxyHandler.GetProxy();
        }
    }
};

Который я звоню, используя

urlList.AsParallel().WithDegreeOfParallelism(12).ForAll(action);

или

Parallel.ForEach(urlList, action);

Мой класс ProxyHandler выглядит следующим образом

public static class ProxyHandler
{    
    static List<Proxy> ProxyList = new ProxyRepository().GetAliveProxies().ToList();

    public static Proxy GetProxy()
    {
        lock (ProxyList)
        {
            while (ProxyList.Count == 0)
            {
                Console.WriteLine("Sleeping");
                Thread.Sleep(1000);
            }
            var proxy = ProxyList[0];
            ProxyList.RemoveAt(0);
            return proxy;
        }           
    }

    public static void ReleaseProxy(Proxy proxy)
    {
        lock (ProxyList)
        {
            if(!ProxyList.Contains(proxy))ProxyList.Add(proxy);
        }
    }

    public static int ListSize()
    {
        lock (ProxyList)
        {
            return ProxyList.Count;
        }
    }
}

Моя проблема в том, что когда он выполняется, кажется, он завершает ~ 90% веб-сайтов очень быстро, а затем занимает очень много времени, чтобы выполнить остальные.

То, что я имею в виду, это то, что из 100 URL-адресов на первые 90-е уходит столько же времени, сколько и на последние 10.

Я исключил, что прокси были мёртвыми, так как исключений не было. Похоже, что последний из пунктов в списке urlList просто занимает очень много времени.

UPDATE:

Я добавляю некоторые текущие данные, чтобы прояснить проблему:

Minute    1 2   3   4   5   6   7   8   9   16  18  19
Count    23 32  32  17  6   1   1   1   1   2   1   2

Как видите, за первые 4 минуты я выполняю 104/119 запросов. А потом все остальное занимает 15 минут.

Это похоже на проблему присоединения к потокам, но я не могу определить, что это может быть.

Ответы [ 2 ]

3 голосов
/ 08 декабря 2011

Вы тратите впустую потоки и процессорное время.В этом случае у вас будет 12 потоков;каждый поток будет обрабатывать только один URL за раз.Таким образом, вы будете обрабатывать только 12 URL за раз.Более того, большую часть времени эти потоки ничего не будут делать (они просто будут ждать свободного прокси-сервера или загруженной страницы), пока их можно будет использовать для более полезных задач.

Чтобы избежать этого, следует использовать неблокировка операций ввода-вывода.Таким образом, вместо использования htmlDocumentLoader.LoadDocument вы должны рассмотреть возможность использования одного из его асинхронных интерфейсов (htmlDocumentLoader.BeginLoadDocument / htmlDocumentLoader.EndLoadDocument или htmlDocumentLoader.LoadDocumentAsync / htmlDocumentLoader.LoadDocumentCompleted).В этом случае, если у вас есть 100 URL-адресов, все они будут загружены одновременно, без создания дополнительных потоков и траты времени процессора.Только при загрузке страницы будет создан новый поток (фактически взятый из ThreadPool) для его обработки.

То, как вы ожидаете получения бесплатного прокси-сервера, также расточительно.Вместо использования while (ProxyList.Count == 0), который замораживает поток в случае отсутствия свободного прокси-сервера, рассмотрите возможность использования таймера, который просыпается каждую секунду и проверяет, доступен ли свободный прокси-сервер.Это не лучшее решение, но, по крайней мере, оно не будет тратить нити.Лучшее решение - добавить событие в ProxyHandler, которое будет уведомлять о доступности прокси.

2 голосов
/ 07 декабря 2011

Ваша проблема, вероятно, связана с тем, что PLinq использует Partitioner.

Если используется Range Partitiner, ваша коллекция URL будет разделена на группы с одинаковым (ish) числом URL в каждой. Затем для каждой группы запускается задача без дальнейшей синхронизации.

Это означает, что будет одна задача, которая занимает больше всего времени и по-прежнему должна работать, когда все другие задачи завершены. Это фактически означает, что последняя часть операции является однопоточной.

Решение - использовать другого Partitioner. Вы можете использовать встроенный разделитель чанков, как описано в MSDN .

Если это не работает достаточно хорошо, вам придется написать / найти реализацию разделителя, которая выдает элементы один за другим. Это встроено в C # 5: EnumerablePartitionerOptions

...