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

С# System.Timers.Timer странное поведение?

Моя цель — написать фрагмент кода, который позволит мне иметь эксклюзивный доступ к объекту (например, txt-файлу) в параллельной среде. Имея это в виду, я тестировал простую программу, построенную на использовании двух таймеров System.Timers. Обработчики событий обоих таймеров совместно используют один и тот же объект блокировки (см. код ниже).
Таймеры запускаются одновременно с разным интервалом: 3 с для таймера 1 и 1 с для таймера 2. Timer1 должен работать только в течение одного цикла, в течение которого его обработчик событий будет спать в течение 10 секунд и, таким образом, сохранять блокировку.
Что меня удивило, так это то, что когда блокировка снимается, я не получаю все события timer2 в памяти (только прил. каждый второй из них). Я думал, пока обработчик событий timer1 имеет блокировку, события timer2 складываются в память. Но это, видимо, неправда. Почему некоторые события timer2 исчезают?

class Program
{
    static int counter = 0;
    static readonly object locker = new object();
    System.Timers.Timer timer1;
    System.Timers.Timer timer2;

    static void Main(string[] args)
    {
        Program p = new Program();

        p.timer1 = new System.Timers.Timer(3000);
        p.timer1.Elapsed += new ElapsedEventHandler(p.Timer1EventHandler);
        p.timer1.Start();
        p.timer2 = new System.Timers.Timer(1000);
        p.timer2.Elapsed += new ElapsedEventHandler(p.Timer2EventHandler);
        p.timer2.Start();
        ThreadPool.SetMaxThreads(50, 50);
        Console.ReadLine();
    }

    void Timer1EventHandler(object sender, ElapsedEventArgs e)
    {
        timer1.Stop();
        DoThingsForTimer1Event();
    }

    void DoThingsForTimer1Event()
    {
        lock (locker)
        {
            Console.WriteLine(DateTime.Now + " Timer1 event started." + " Current thread number " + Thread.CurrentThread.ManagedThreadId);

            Thread.Sleep(10000);

            Console.WriteLine(DateTime.Now + " Timer1 event finished. Lock released.");
        }

    }

    void Timer2EventHandler(object sender, ElapsedEventArgs e)
    {
        counter++;
        lock (locker)
        {
            Console.WriteLine(DateTime.Now + " Timer2 event fired. Current thread number " + Thread.CurrentThread.ManagedThreadId +
                " Counter=" + counter);
        }                                         
    }
}

введите здесь описание изображения


  • I don't get all stacked in memory timer2 events что вы имеете в виду под этим, кажется, это работает так, как вы ожидаете 07.11.2018
  • @TheGeneral Когда я это читал, он ожидал, что события Timer2 Tick будут накапливаться в памяти до тех пор, пока блокировка не будет снята. То, что происходит, когда таймер срабатывает чаще, чем его события Tick, можно обработать. 07.11.2018
  • Хотя, если я не читаю это неправильно, это именно то, что он сделал 07.11.2018
  • Если вы запутались, куда делись 3-8, переместите ++ внутрь замка. 07.11.2018
  • С практической точки зрения вы также не можете безопасно использовать ++ из нескольких потоков одновременно. Поэтому, если вы не поместите их внутрь замка, вы должны использовать Interlocked.Increment. Это пока не является серьезной проблемой для вас (поскольку условия гонки редки), но все же остается проблемой. 07.11.2018
  • @mjwills Они складываются. Истинный. Но не все. Как видите, есть 10-секундная пауза, в течение которой timer2 должен генерировать события каждую секунду, т.е. 10 событий. В 14:36:21 видим что вышло всего 7 07.11.2018
  • Я думаю, что ему просто надоело давать вам темы лично, я имею в виду, что пул потоков просто не будет отказываться от потоков так быстро, как вы этого хотите, скорее всего. я думаю, если вы измените тайминг на 5-секундную блокировку, он, скорее всего, будет работать так, как ожидалось. 07.11.2018
  • stackoverflow.com/a/10298760/34092 и stackoverflow.com/a/37611613/34092 стоит прочитать. 07.11.2018
  • Другой способ сделать это — вернуть потоки в пул. вместо использования блокировки используйте SemaphoreSlim(1,1) между двумя событиями, а затем ожидайте WaitAsync, когда очереди (хотя и неэффективные) вернут ваши потоки, осчастливив планировщик. Тем не менее, я думаю, вам следует переосмыслить весь дизайн, не использовать такие потоки locking и не использовать Thread.Sleep, не блокировать потоки пула потоков. 07.11.2018

Ответы:


1

Спасибо @TheGeneral за то, что он определил это как основную причину проблемы ОП.

Основная проблема, с которой вы здесь сталкиваетесь, заключается в том, что ваш ThreadPool исчерпан (а ваш Timer использует ThreadPool), поскольку у вас есть ЦП только с 4 логическими ядрами. Это объясняет, почему я лично (с 12 ядрами) не могу это воспроизвести.

Согласно документы:

По умолчанию минимальное количество потоков равно количеству процессоров в системе.

Таким образом, планировщик пула потоков, вероятно, начинает с 4 потоков. Планировщик пула потоков довольно консервативен. Он не просто распределяет потоки по вашему запросу — иногда он -is-better-to-do-thread-manual-in/10298760#10298760">задерживает их создание, чтобы повысить общую производительность системы (поскольку запуск потоков обходится дорого).

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

ThreadPool.SetMinThreads(50, 50);

Это быстро увеличит его до 50, а затем более консервативно.

Однако в долгосрочной перспективе проблема заключается в том, что вы выполняете длительные операции в пуле потоков. Это плохая идея. Вы можете переместить их в потоки или в длительные задачи (которые на практике являются потоками). Но у обоих этих вариантов есть обратная сторона. По сути, вы хотите, чтобы длительные операции выполнялись вне пула потоков, если это возможно.

Не понимая, почему вы используете lock, трудно дать хороший совет. Но один вариант, который можно рассмотреть, может состоять в том, чтобы использовать BlockingCollection для формирования очереди, а затем иметь один отдельный поток, обрабатывающий эту очередь. Это означает, что ваши события Timer просто добавят запись в очередь, а затем вернутся — основная часть обработки будет в (единственном) потоке, который обрабатывает записи из очереди.

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

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

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

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

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

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

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

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