Nano Hash - криптовалюты, майнинг, программирование

Шаблон проектирования, альтернатива сопрограммам

В настоящее время у меня есть большое количество вычислений C# (вызовов методов), находящихся в очереди, которые будут выполняться последовательно. Каждое вычисление будет использовать некоторую службу с высокой задержкой (сеть, диск...).

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

Существует ли шаблон проектирования, реализуемый на чистом C#, который позволит мне выполнять дополнительные вычисления в ожидании возврата служб с высокой задержкой?

Спасибо

Обновление:

Мне нужно выполнить огромное количество (> 10000) задач, и каждая задача будет использовать какой-либо сервис с высокой задержкой. В Windows вы не можете создать столько потоков.

Обновление:

По сути, мне нужен шаблон проектирования, который эмулирует преимущества тасклетов в Stackless Python (http://www.stackless.com/)

  1. Огромное количество задач
  2. Если задача блокирует выполнение следующей задачи в очереди
  3. Нет потраченного впустую цикла процессора
  4. Минимальные накладные расходы на переключение между задачами

  • Можете ли вы привести веские доводы в пользу сопрограмм как решения? Кажется, требуется (сбалансированная) многопоточность, как в ответе dtb. 24.08.2009
  • Ну, мне нужно выполнить огромное количество (>10000) задач, и каждая задача будет использовать какой-то сервис с высокой задержкой. В Windows вы не можете создать столько потоков. 24.08.2009
  • Звучит как работа для ThreadPool, +1 для jscharf 24.08.2009

Ответы:


1

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

Рассмотрим метод

IEnumerable Thread ()
{
    //do some stuff
    Foo ();

    //co-operatively yield
    yield null;

    //do some more stuff
    Bar ();

    //sleep 2 seconds
    yield new TimeSpan (2000);
}

Компилятор C# развернет это в конечный автомат, но внешний вид будет похож на кооперативный микропоток.

Схема довольно проста. Вы реализуете «планировщик», который хранит список всех активных IEnumerators. Когда он циклически проходит по списку, он «запускает» каждый из них, используя MoveNext(). Если значение MoveNext равно false, поток завершен, и планировщик удаляет его из списка. Если это правда, то планировщик обращается к свойству Current, чтобы определить текущее состояние потока. Если это TimeSpan, поток хочет заснуть, и планировщик переместил его в некоторую очередь, которую можно сбросить обратно в основной список, когда промежутки времени ожидания закончились.

Вы можете использовать другие возвращаемые объекты для реализации других механизмов сигнализации. Например, определите какой-нибудь WaitHandle. Если поток выдает один из них, он может быть перемещен в очередь ожидания до тех пор, пока дескриптор не будет сигнализирован. Или вы можете поддержать WaitAll, создав массив дескрипторов ожидания. Вы даже можете реализовать приоритеты.

Я сделал простую реализацию этого планировщика примерно за 150LOC, но еще не успел опубликовать код в блоге. Это было для нашей оболочки PhyreSharp PhyreEngine (которая не будет общедоступной), где, похоже, она неплохо работает для управления парой сотен персонажей в одной из наших демонстраций. Мы позаимствовали эту концепцию из движка Unity3D — у них есть несколько онлайн-документов, которые объясняют ее с точки зрения пользователя.

24.08.2009
  • Интересный материал. Я смотрел на это раньше, но я не думаю, что вы можете правильно выйти из кода глубиной более 1 уровня. т.е. если у вас есть функция запуска сопрограммы, которая вызывает другую функцию, которая дает результат, все ломается. 24.08.2009
  • В большинстве случаев вы можете просто реализовать функции, которые вы хотите вызывать, как ienumerables и foreach+yield над ними, хотя, конечно, вам потребуется немного косвенности, чтобы получить возвращаемые значения - я не могу вспомнить, работают ли параметры ref с выход, но если нет, есть множество других способов. Вы можете передать ссылку на объект с полями и использовать его, чтобы получить значения обратно из функции, или передать лямбда-выражения, которые могут установить ваши локальные значения, или отфильтровать определенный тип из полученных значений и т. д. 25.08.2009
  • т.е. Вещь возвращена = ноль; foreach (var o в Function ((x) =› return = x)) yield return o; 25.08.2009

  • 2

    .NET 4.0 поставляется с расширенной поддержкой параллелизма задач. :

    23.08.2009
  • Да, это не решает проблему продолжения следующего вычисления, когда есть операция с интенсивными задержками. Параллелизм задач просто упрощает параллельные вычисления. 23.08.2009
  • параллельная библиотека задач может свободно использовать больше потоков, чем ядер, поэтому, если она обнаружит, что используемые потоки не используют много процессорного времени, она может запланировать больше задач... Это может привести к чрезмерному вводу-выводу, поэтому требуется тщательная настройка, есть надежда что библиотека многое делает за вас, но бенчмаркинг и проверка всегда хорошая идея... 23.08.2009
  • Настоящие потоки слишком тяжеловесны для этого проекта. Мне нужно 20-80 тысяч вычислительных задач, выполняемых одновременно. 23.08.2009
  • Именно для этого и была разработана параллельная библиотека задач. Просто запустите свои задачи и позвольте библиотеке назначить их ядрам/потокам. 23.08.2009
  • Будет ли библиотека параллелизма задач создавать новые потоки, если существующие потоки ожидают? 23.08.2009
  • Библиотека создаст оптимальное количество потоков и постоянно регулирует это число, чтобы поддерживать его оптимальным. Итак, да, он создаст новые потоки, если это повысит производительность. 23.08.2009
  • TPL динамически масштабирует степень параллелизма для наиболее эффективного использования всех доступных процессоров. Из документации Microsoft TPL. Я настоятельно рекомендую ознакомиться с TPL... похоже, это идеальное решение вашей проблемы. 24.08.2009

  • 3

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

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

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

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

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

    Вы также можете реализовать гибкость в размере и содержимом списка активных задач, ограничив его определенным количеством задач определенного типа. Например, если вы работаете на машине с 4 ядрами, вы можете обнаружить, что самая производительная конфигурация — это четыре задачи, привязанные к ЦП, которые выполняются одновременно, а также одна задача, привязанная к диску, и сетевая задача.

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

    Надеюсь, это имеет смысл!

    PS: Есть ли у вас зависимости от порядка задач?

    24.08.2009
  • Нет. Требований к порядку исполнения нет. 24.08.2009
  • Дай мне посмотреть, получил ли я это. Для каждого ядра определенное количество потоков будет находиться в пуле потоков. Первоначально задача назначается потоку и выполняется. Каждый раз, когда задача блокируется (ввод-вывод, ...), задача уведомляет/пробуждает поток контроллера для этого ядра ЦП, а контроллер запускает новый поток или пробуждает ранее спящий. Так продолжается до тех пор, пока не будут обработаны все задачи. 24.08.2009
  • Вы немного не в себе, но я думаю, вы поняли суть. Вы должны прочитать документацию ThreadPool (или Google для некоторых руководств, использующих QueueUserWorkItem). На самом деле для каждого ядра не создаются потоки. Думайте о ThreadPool как об абстракции, не зависящей от ядер. Вы просто бросаете на него несколько задач, которые вам нужно запланировать и выполнять одновременно, когда это возможно (что обычно и происходит). 24.08.2009
  • Приведенное вами решение приводит к двум сценариям: оно либо создает поток для каждой задачи, либо распределяет задачи между несколькими потоками (где количество потоков близко к количеству ядер), и эти потоки выполняют назначенные задачи. к нему последовательно. Первый сценарий невозможен, так как будет слишком много потоков (количество задач > 10000). Во втором сценарии, поскольку задачи в потоке выполняются последовательно, блокирующая операция приведет к трате циклов ЦП. 24.08.2009
  • Опубликованное мной решение позволяет избежать вашего первого сценария. Вы не будете ставить в очередь все 10000+ задач одновременно. Вы будете ставить в очередь небольшое количество (например, 5) сразу для отправки в ThreadPool. Когда задача в пуле завершена, она удаляется из списка «активных задач» и добавляется еще одна. Поддерживая свой собственный список «активных задач», выполняемых пулом, вы можете быть уверены, что никогда не будет выполняться громоздкое количество потоков. 24.08.2009
  • Кроме того, если вы правильно используете асинхронный ввод-вывод, ваши потоки задач должны спать, ожидая завершения своих операций, связанных с задержкой, что позволяет эффективно планировать потоки для всех активных задач. 24.08.2009
  • Кажется, я забыл упомянуть об этом раньше, но почти всегда каждая задача будет ожидать выполнения какой-либо операции с высокой задержкой. В приведенном выше сценарии будет ожидать огромное количество спящих потоков. 24.08.2009
  • @jscharf и jameszhao00, по умолчанию ThreadPool содержит 25 потоков, что намного больше, чем # ядер, но все же разумно. Вы могли бы настроить его. Основное преимущество заключается в том, чтобы избежать накладных расходов на создание (многих) потоков. Ограничение и очередь управляют рабочей нагрузкой, но TPL делает это более продвинуто. 24.08.2009

  • 4

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

    26.08.2009

    5

    Разве это не обычное использование многопоточной обработки?

    Ознакомьтесь с такими шаблонами, как Reactor здесь

    23.08.2009
  • Прости. Я немного смущен тем, как это можно использовать здесь. 23.08.2009

  • 6

    Может быть достаточно написать его для использования асинхронного ввода-вывода. .

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

    23.08.2009
  • На нижнем уровне да, я буду использовать AsyncIO для отправки/получения сетевых пакетов. Однако на более высоких уровнях я буду реализовывать своего рода синхронный RPC. 23.08.2009

  • 7

    Вы должны взглянуть на это:

    http://www.replicator.org/node/80

    Это должно делать именно то, что вы хотите. Однако это взлом.

    23.11.2010

    8

    Еще немного информации о шаблоне «Реактивный» (как упоминалось другим автором) в отношении реализации в .NET; также известный как «Связь с событиями»

    http://themechanicalbride.blogspot.com/2009/07/introduction-rx-linq-to-events.html

    -Ойсин

    24.08.2009
  • Это похоже на идею таблицы диспетчера вызовов. Я не совсем понимаю, как это может решить проблему под рукой. 24.08.2009

  • 9

    На самом деле, если вы используете один поток для задачи, вы проиграете игру. Подумайте, почему Node.js может поддерживать огромное количество соединений. Использование нескольких потоков с асинхронным вводом-выводом!!! В этом могут помочь функции Async и await.

    foreach (var task in tasks)
    {
        await SendAsync(task.value);
        ReadAsync(); 
    }
    

    SendAsync() и ReadAsync() — это поддельные функции для асинхронного вызова ввода-вывода.

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

    28.12.2013

    10

    Да, конечно ты можешь. Вам просто нужно создать диспетчерский механизм, который будет вызывать предоставленную вами лямбду и ставить ее в очередь. Весь код, который я пишу в Unity, использует этот подход, и я никогда не использую сопрограммы. Я оборачиваю методы, которые используют сопрограммы, такие как WWW, чтобы просто избавиться от них. Теоретически сопрограммы могут быть быстрее, потому что меньше накладных расходов. Практически они вводят новый синтаксис в язык для выполнения довольно тривиальной задачи, и, кроме того, вы не можете правильно отследить трассировку стека при ошибке в сопрограмме, потому что все, что вы увидите, это ->Next. Затем вам нужно будет реализовать возможность запуска задач в очереди в другом потоке. Однако в последней версии .net есть параллельные функции, и вы, по сути, будете писать аналогичные функции. На самом деле это не будет много строк кода.

    Если кому интересно, вышлю код, не носите его с собой.

    20.02.2014
    Новые материалы

    Кластеризация: более глубокий взгляд
    Кластеризация — это метод обучения без учителя, в котором мы пытаемся найти группы в наборе данных на основе некоторых известных или неизвестных свойств, которые могут существовать. Независимо от..

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..