2013-12-19 3 views
4

У меня высокая пропускная способность Singleton в многопоточной среде. Обычно я хотел бы сделать что-то вроде этого:Является ли этот многопоточный Singleton более эффективным?

public static Foo GetInstance() 
{ 
    lock (Foo._syncLock) 
    { 
     if (Foo._instance == null) 
      Foo._instance = new Foo(); 
     return Foo._instance; 
    } 
} 

Я интересно, если делать следующее вместо будет более эффективным, так как это позволит избежать постоянного блокирования потоков, или есть скрытые проблемы с этим?

public static Foo GetInstance() 
{ 
    if (Foo._instance != null) 
     return Foo._instance; 
    lock (Foo._syncLock) 
    { 
     if (Foo._instance == null) 
      Foo._instance = new Foo(); 
     return Foo._instance; 
    } 
} 
+10

Вы можете сделать лучше: http://csharpindepth.com/Articles/General/Singleton.aspx – MarcinJuraszek

+0

Хорошая ссылка, я ее читаю. –

+0

Никто не мог сказать, является ли ваш данный код потокобезопасным или нет, не видя определения '_instance'. – Voo

ответ

9

Мне интересно, будет ли выполнение следующего вместо этого более эффективным, поскольку это позволит избежать непрерывной блокировки потока или скрытых проблем с ним?

Ваш вопрос: «Будет ли выигрыш в производительности, перейдя на опасный шаблон с низким уровнем блокировки?» То есть совершенно неправильный вопрос, чтобы задать. Никогда не рассуждайте так! Таким образом, это потраченное впустую время, потраченное впустую усилие и безумные, невозможные для отладки ошибки.

Правильный вопрос: «Мои измерения сильно указывают, что у меня есть проблема с производительностью?» ».

Если ответ «нет», значит, все готово.

Только если ответ «да», если вы задать следующий вопрос, который «Могу ли я устранить мою проблему производительности за счет устранения разногласий на замок?»

Если ответ «да», то устранить конфликт на замке и вернуться к первому вопросу.

Только если ответ «нет», тогда вы должны задать следующий вопрос: «Пойдет ли решение с низкой блокировкой, даст приемлемую производительность

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

В невероятно маловероятно, что ответ «да», вы должны перейти к следующему вопросу, который является «ли правильное выполнение блокировки с двойной проверкой устранить мою проблему производительности?»

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

Только если ответ на этот вопрос «да», если вы реализуете блокировку с двойной проверкой.

Теперь давайте прийти к вам актуальный вопрос:

есть ли скрытые проблемы с этим?

Ваша реализация верна. Однако, как только вы отклонились от блаженного паттерна, все ставки отключены. Например:

static object sync = new object(); 
static bool b = false; 
static int x = 0; 
static int GetIt() 
{ 
    if (!b) 
    { 
    lock(sync) 
    { 
     if (!b) 
     { 
     b = true; 
     x = ExpensiveComputation(); 
     } 
    } 
    } 
    return x; 
} 

Это выглядит правильно, правда? Но это неверно! Рассмотрим путь с низкой блокировкой. Поскольку на этом пути нет препятствий, значение x может быть предварительно задано как ноль в одном потоке, а затем другой поток может выполняться и устанавливать b в true и x на 123, а затем исходный поток может извлекать b, get true и вернуть предварительно выбранный x.

Итак, какие решения? В моем порядке предпочтения:

  • Не ленитесь. Инициализируйте статическое поле один раз и сделайте с ним.
  • Используйте блаженный ленивый одноэлементный образец, задокументированный на веб-сайте Джона Скита.
  • Использование Lazy<T>.
  • Использовать блокировку с одной проверкой.
  • Если вас не волнует, что синглтон находится в редких ситуациях, созданных дважды, а один отбрасывается, используйте InterlockedCompareExchange.
  • Используйте благословенный двойной замок.
+0

Спасибо, я инициализирую статическое поле одним экземпляром. Я также ценю детали вашего ответа. –

+0

@ Эрик: Вы забыли установить b в true, что приведет к другому состоянию гонки? Когда значение b установлено в true, вы все равно сможете получить x, который является нулевым, когда ваш поток читает из двух разных строк кеша. По крайней мере, на моем Core I7 я не могу заставить это потерпеть неудачу. Вы говорите о моделях памяти x86, таких как ARM? –

+0

@AloisKraus: Я исправил опечатку, спасибо. Приведенный здесь сломанный шаблон не терпит неудачу на x86 из-за сильных гарантий памяти модели x86. –

0

избежать блокировки всегда хорошая идея! Но ... нормальный путь будет:

Оригинальный код:

if(_instance == null) 
    lock(_syncRoot) 
    if(_instance == null) 
     _instance = new Foo(); 

return _instance; 

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

Идея состоит в том, что после релизов блокировки отдельный поток мог инициализировать объект. Поэтому вы должны проверить еще раз.

Замечание

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

Редактировать

Per замечания: Лучше метод должен был бы позволить структуру экземпляра статического члена. Если я правильно понимаю, это устранит необходимость в любом виде locking.

private static Foo _instance = new Foo(); 

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

+1

Нормальный? Как это нормально? 'private static Foo instance = new Foo();' короче, легче понять, более эффективно и более распространенная практика. – Servy

+0

@Servy Обычный путь для ленивой инициализации довольно очевидно. Тем не менее, код потенциально не работает. – Voo

+0

@ Voo То, что я только что описал *, - это ленивая инициализация. Статические поля не инициализируются до тех пор, пока классы не будут использованы. – Servy

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