2015-05-19 4 views
2

В моей программе .NET я хочу подсчитать количество ударов фрагмента кода. Чтобы сделать его более сложным, мой код обычно выполняется в нескольких потоках, и я не могу контролировать создание/уничтожение потоков (и не знаю, когда они созданы) ... их можно даже объединить. Скажи:Подсчет материалов в нескольких потоках

class Program 
{ 
    static int counter = 0; 

    static void Main(string[] args) 
    { 
     Stopwatch sw = Stopwatch.StartNew(); 

     Parallel.For(0, 100000000, (a) => 
      { 
       Interlocked.Increment(ref counter); 
      }); 

     Console.WriteLine(sw.Elapsed.ToString()); 
    } 
} 

Как счетчик производительности и метода достаточно несколько раз ударил, я хотел бы использовать «нормальные» переменный, в отличии от атомарных/сблокирован целого. Моя вторая попытка состояла в том, чтобы использовать threadlocal storage в сочетании с IDisposable для ускорения работы. Потому что я не могу контролировать создание/уничтожение, я должен следить за переменными хранения:

class Program 
{ 
    static int counter = 0; 

    // I don't know when threads are created/joined, which is why I need this: 
    static List<WeakReference<ThreadLocalValue>> allStorage = 
     new List<WeakReference<ThreadLocalValue>>(); 

    // The performance counter 
    [ThreadStatic] 
    static ThreadLocalValue local; 

    class ThreadLocalValue : IDisposable 
    { 
     public ThreadLocalValue() 
     { 
      lock (allStorage) 
      { 
       allStorage.Add(new WeakReference<ThreadLocalValue>(this)); 
      } 
     } 

     public int ctr = 0; 

     public void Dispose() 
     { 
      // Atomic add and exchange 
      int tmp = Interlocked.Exchange(ref ctr, 0); // atomic set to 0-with-read 
      Interlocked.Add(ref Program.counter, tmp); // atomic add 
     } 

     ~ThreadLocalValue() 
     { 
      // Make sure it's merged. 
      Dispose(); 
     } 
    } 

    // Create-or-increment 
    static void LocalInc() 
    { 
     if (local == null) { local = new ThreadLocalValue(); } 
     ++local.ctr; 
    } 

    static void Main(string[] args) 
    { 
     Stopwatch sw = Stopwatch.StartNew(); 

     Parallel.For(0, 100000000, (a) => 
      { 
       LocalInc(); 
      }); 

     lock (allStorage) 
     { 
      foreach (var item in allStorage) 
      { 
       ThreadLocalValue target; 
       if (item.TryGetTarget(out target)) 
       { 
        target.Dispose(); 
       } 
      } 
     } 

     Console.WriteLine(sw.Elapsed.ToString()); 

     Console.WriteLine(counter); 
     Console.ReadLine(); 
    } 
} 

Мой вопрос: можем ли мы сделать это быстрее и/или симпатичнее?

+0

Знаете ли вы, что доступ к полю 'ThreadStatic' будет намного медленнее, чем общий атомный приращение. Вы делаете это хуже. Вы сталкиваетесь с проблемами производительности? –

+0

@SriramSakthivel Запустите тесты, пожалуйста. Исходная версия с 'Interlocked.Increment' выполняется за 1,6 секунды, а вторая версия - 0,2 секунды. – atlaste

+0

@atlaste Вы были правы ... Существует еще одна потенциальная ошибка: 'Dispose()' может вызываться дважды: 'GC' запускается после конца потока, см., Что' ThreadLocalValue' имеет финализатор и помещает его в очередь завершения. TLV не GCed в этот момент. Очередь финализатора работает в другом потоке и решает запустить. 'Dispose()' выполняется в потоке финализатора, в то время как «основной» поток загружает WF для TLV, преуспевая, потому что TLV не был собран, поэтому «основной» поток выполняет «Dispose()». – xanatos

ответ

1

Для выполнения подсчета вам нужна потокобезопасная, неблокирующая, volatile, static.


Благодаря благость, рамки .NET предоставляет управляемые способы выполнения того, что вы хотите.

Для начала вам нужна изменчивая статическая переменная, которая будет использоваться в качестве счетчика. Объявите это нравится (где все ваши потоки могут получить доступ к нему):

public static volatile int volatileCounter; 

Где статический означает, что это класс и не является членом экземпляра и летучий предотвращает ошибки кэширования от происходящего.

Далее вам понадобится код, который увеличивает его поточно-безопасным и неблокирующим способом. Если вы не ожидаете, что ваш счетчик превышает пределы переменной Int (что весьма вероятно), вы можете использовать Interlocked class для этого типа:

Interlocked.Increment(ref yourInstance.volatileCounter); 

interlocked class будет гарантировать, что ваша операция инкремента будет atom, поэтому никакое состояние гонки не может вызывать ложные результаты, а также не блокируется в режиме тяжелого взвешивания sync objects, и здесь задействована блокировка потоков.

+0

Эм ... с этим я начал свой вопрос? Решение 'ThreadStatic' на самом деле намного быстрее. – atlaste

+0

@atlaste Откуда вы начали с этим вопрос? Я перечитал и не смог его найти. Кроме того, зная базовую систему в деталях, я сильно сомневаюсь, что существует поточно-безопасное решение, которое быстрее моего. Я могу представить, что решение будет в равной степени быстрым при определенных условиях, но это все (потому что переменная ** имеет ** неустойчивую, чтобы избежать ошибок кэширования, а самый быстрый способ выполнить неблокирующее потокобезопасное увеличение - _InterlockedIncrement() _, который вызывается блокированным классом в фоновом режиме). – mg30rg

+0

Ок, кажется, мы неправильно понимаем друг друга. Я начал с реализации 'Interlocked.Increment' в моем вопросе, после чего я заметил:« Я бы хотел использовать «нормальную» переменную в отличие от атомарного/блокированного целого числа ». Требование состоит в том, что счетчик производительности должен иметь правильное значение _after_ выполнение программы. Согласованность потоков во время выполнения не является обязательным требованием. Из-за этого ослабленного ограничения вы можете утверждать, что 'Interlocked' слишком тяжелый вес для того, что я хочу сделать, и поэтому лучше реализовать реализацию без атомного доступа. – atlaste

Смежные вопросы