Подтвердить что ты не робот

С# Thread безопасный быстрый (est) счетчик

Каков способ получения счетчика потокобезопасности в С# с наилучшей производительностью?

Это так просто, как это получается:

public static long GetNextValue()
{
    long result;
    lock (LOCK)
    {
        result = COUNTER++;
    }
    return result;
}

Но есть ли более быстрые альтернативы?

4b9b3361

Ответ 2

Как рекомендовано другими, Interlocked.Increment будет иметь лучшую производительность, чем lock(). Просто взгляните на IL и Assembly, где вы увидите, что Increment превращается в оператор "блокировки шины", и его переменная напрямую увеличивается (x86) или "добавлена" на (x64).

Этот оператор "блокировки шины" блокирует шину, чтобы другой процессор не мог получить доступ к шине, в то время как вызывающий ЦП выполняет свою работу. Теперь посмотрим на инструкцию С# lock() IL. Здесь вы увидите вызовы Monitor, чтобы начать или завершить раздел.

Другими словами, оператор .Net lock() делает намного больше, чем .Net Interlocked.Increment.

SO, если все, что вы хотите сделать, это увеличение переменной, Interlock.Increment будет быстрее. Просмотрите все методы блокировки, чтобы увидеть различные доступные атомные операции и найти те, которые соответствуют вашим потребностям. Используйте lock(), если вы хотите выполнять более сложные вещи, например, несколько взаимосвязанных приращений/декрементов или сериализовать доступ к ресурсам, которые более сложны, чем целые числа.

Ответ 3

Я предлагаю вам использовать .NET, встроенный в инкремент блокировки в библиотеке System.Threading.

Следующий код будет увеличивать длинную переменную по ссылке и полностью потокобезопасен:

Interlocked.Increment(ref myNum);

Источник: http://msdn.microsoft.com/en-us/library/dd78zt0c.aspx

Ответ 5

Как уже упоминалось, используйте Interlocked.Increment

Пример кода от MS:

В следующем примере определяется, сколько случайных чисел в диапазоне от 0 до 1000 требуется для генерации 1000 случайных чисел со значением средней точки. Для отслеживания количества значений средней точки переменная midpointCount устанавливается равной 0 и увеличивается каждый раз, когда генератор случайных чисел возвращает значение средней точки, пока оно не достигнет 10000. Поскольку три потока генерируют случайные числа, вызывается метод Increment (Int32), чтобы несколько потоков не обновляли midpointCount одновременно. Обратите внимание, что блокировка также используется для защиты генератора случайных чисел и что объект CountdownEvent используется для гарантии того, что метод Main не завершит выполнение до трех потоков.

using System;
using System.Threading;

public class Example
{
   const int LOWERBOUND = 0;
   const int UPPERBOUND = 1001;

   static Object lockObj = new Object();
   static Random rnd = new Random();
   static CountdownEvent cte;

   static int totalCount = 0;
   static int totalMidpoint = 0;
   static int midpointCount = 0;

   public static void Main()
   {
      cte = new CountdownEvent(1);
      // Start three threads. 
      for (int ctr = 0; ctr <= 2; ctr++) {
         cte.AddCount();
         Thread th = new Thread(GenerateNumbers);
         th.Name = "Thread" + ctr.ToString();
         th.Start();
      }
      cte.Signal();
      cte.Wait();
      Console.WriteLine();
      Console.WriteLine("Total midpoint values:  {0,10:N0} ({1:P3})",
                        totalMidpoint, totalMidpoint/((double)totalCount));
      Console.WriteLine("Total number of values: {0,10:N0}", 
                        totalCount);                  
   }

   private static void GenerateNumbers()
   {
      int midpoint = (UPPERBOUND - LOWERBOUND) / 2;
      int value = 0;
      int total = 0;
      int midpt = 0;

      do {
         lock (lockObj) {
            value = rnd.Next(LOWERBOUND, UPPERBOUND);
         }
         if (value == midpoint) { 
            Interlocked.Increment(ref midpointCount);
            midpt++;
         }
         total++;    
      } while (midpointCount < 10000);

      Interlocked.Add(ref totalCount, total);
      Interlocked.Add(ref totalMidpoint, midpt);

      string s = String.Format("Thread {0}:\n", Thread.CurrentThread.Name) +
                 String.Format("   Random Numbers: {0:N0}\n", total) + 
                 String.Format("   Midpoint values: {0:N0} ({1:P3})", midpt, 
                               ((double) midpt)/total);
      Console.WriteLine(s);
      cte.Signal();
   }
}
// The example displays output like the following:
//       Thread Thread2:
//          Random Numbers: 2,776,674
//          Midpoint values: 2,773 (0.100 %)
//       Thread Thread1:
//          Random Numbers: 4,876,100
//          Midpoint values: 4,873 (0.100 %)
//       Thread Thread0:
//          Random Numbers: 2,312,310
//          Midpoint values: 2,354 (0.102 %)
//       
//       Total midpoint values:      10,000 (0.100 %)
//       Total number of values:  9,965,084

Следующий пример аналогичен предыдущему, за исключением того, что он использует класс Task вместо процедуры потока для генерации 50 000 случайных целых чисел в средней точке. В этом примере лямбда-выражение заменяет процедуру потока GenerateNumbers, а вызов метода Task.WaitAll устраняет необходимость в объекте CountdownEvent.

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

public class Example
{
   const int LOWERBOUND = 0;
   const int UPPERBOUND = 1001;

   static Object lockObj = new Object();
   static Random rnd = new Random();

   static int totalCount = 0;
   static int totalMidpoint = 0;
   static int midpointCount = 0;

   public static void Main()
   {
      List<Task> tasks = new List<Task>();
      // Start three tasks. 
      for (int ctr = 0; ctr <= 2; ctr++) 
         tasks.Add(Task.Run( () => { int midpoint = (UPPERBOUND - LOWERBOUND) / 2;
                                     int value = 0;
                                     int total = 0;
                                     int midpt = 0;

                                     do {
                                        lock (lockObj) {
                                           value = rnd.Next(LOWERBOUND, UPPERBOUND);
                                        }
                                        if (value == midpoint) { 
                                           Interlocked.Increment(ref midpointCount);
                                           midpt++;
                                        }
                                        total++;    
                                     } while (midpointCount < 50000);

                                     Interlocked.Add(ref totalCount, total);
                                     Interlocked.Add(ref totalMidpoint, midpt);

                                     string s = String.Format("Task {0}:\n", Task.CurrentId) +
                                                String.Format("   Random Numbers: {0:N0}\n", total) + 
                                                String.Format("   Midpoint values: {0:N0} ({1:P3})", midpt, 
                                                              ((double) midpt)/total);
                                     Console.WriteLine(s); } ));

      Task.WaitAll(tasks.ToArray());
      Console.WriteLine();
      Console.WriteLine("Total midpoint values:  {0,10:N0} ({1:P3})",
                        totalMidpoint, totalMidpoint/((double)totalCount));
      Console.WriteLine("Total number of values: {0,10:N0}", 
                        totalCount);                  
   }
}
// The example displays output like the following:
//       Task 3:
//          Random Numbers: 10,855,250
//          Midpoint values: 10,823 (0.100 %)
//       Task 1:
//          Random Numbers: 15,243,703
//          Midpoint values: 15,110 (0.099 %)
//       Task 2:
//          Random Numbers: 24,107,425
//          Midpoint values: 24,067 (0.100 %)
//       
//       Total midpoint values:      50,000 (0.100 %)
//       Total number of values: 50,206,378

https://docs.microsoft.com/en-us/dotnet/api/system.threading.interlocked.increment?view=netcore-3.0