C # Нитка безпечна швидка (est) лічильник


147

Який спосіб отримати безпечний лічильник потоку в C # з найкращими можливими показниками?

Це так просто, як це виходить:

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

Але чи існують швидші альтернативи?

Відповіді:



108

Як рекомендують інші, Interlocked.Incrementволя матиме кращі показники, ніж lock(). Просто подивіться на ІР та збори, де ви це побачитеIncrement перетворюється на заяву "блокування шини", а його змінна безпосередньо збільшується (x86) або "додається" до (x64).

Це твердження "блокування шини" блокує шину, щоб запобігти доступу іншого шини до процесора, поки викликає процесор виконує свою роботу. А тепер подивіться на lock()ІР заявки C # . Тут ви побачите дзвінки Monitor, щоб розпочати або закінчити розділ.

Іншими словами, .Net lock()операція робить набагато більше, ніж .Net Interlocked.Increment.

Так, якщо все, що ви хочете зробити, це збільшення змінної, Interlock.Incrementбуде швидше. Перегляньте всі перемикані методи, щоб побачити різні доступні атомні операції та знайти ті, що відповідають вашим потребам. Використовуйте, lock()коли ви хочете робити більш складні речі, такі як кілька взаємопов'язаних приростів / декрементів, або серіалізувати доступ до ресурсів, складніших за цілі числа.


3
-1 для деталей реалізації. Це правда, що блокування відбувається повільніше, ніж атомна оп, але це не має нічого спільного з ІЛ. Ці виклики функцій були б набагато швидшими, ніж атомний оп, якби не їх семантика, що не вимагає від ІЛ.
Щеня

33

Я пропоную вам використовувати .NET вбудований з кроком блокування в бібліотеці System.Threading.

Наступний код збільшує довгу змінну за посиланням і повністю безпечний для потоків:

Interlocked.Increment(ref myNum);

Джерело: http://msdn.microsoft.com/en-us/library/dd78zt0c.aspx



1

Як вже згадувалося використання 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

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.