блокировка против мьютекса, проблемы масштабирования

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

Последовательность:

Потоки (ожидание соединения) -> Асинхронный обратный вызов -> Сделать что-нибудь потокобезопасное -> Доступ к общему объекту -> Сделать что-нибудь потокобезопасное.

1. Мьютекс решения:

Object sharedObject = new Object();
Mutex objectMutex = new Mutex();

void threadCallback()
{
  Object newObject = new Object();

  // some processing

  objectMutex.lock();
  // do exchange sharedObject with newObject if needed
  // very little processing here
  objectMutex.unlock();

  // some processing
}

2. Блокировка решения

Object sharedObject = new Object();
int usingSharedObject = 0;

void threadCallback()
{
  Object newObject = new Object();

  // some processing

  // poll until we lock
  while(1 == Interlocked.Exchange(ref usingSharedObject , 1))
  {
    // do exchange sharedObject with newObject if needed
    // very little processing here
    Interlocked.Exchange(ref usingSharedObject , 0); // free lock
  }

  // some processing
}

Что быстрее и лучше масштабируется?

Я ожидаю, что второе решение будет быстрее, если одновременно не будет опрашиваться много потоков. Второе решение может даже засыпать в случайное время, чтобы опрос не поглощал время обработки. Первое решение кажется мне чище, если мне действительно нужно обработать много TCP/IP-соединений. Поскольку я очень мало обрабатываю в заблокированном разделе, касающемся обработки TCP/IP, возникнут ли какие-либо проблемы с масштабированием?

Как насчет создания объекта в начале функции threadCallback().

В моем опыте C++ я всегда использовал пулы памяти в такой ситуации, так как я должен использовать безопасный .NET, есть ли быстрый способ создавать новые объекты или платформа .NET хорошо работает в этой области.

С наилучшими пожеланиями,

Фридрих


person Friedrich    schedule 03.02.2010    source источник
comment
Есть ли гарантия, что правильный поток владеет блокировкой в ​​заблокированном примере?   -  person Lasse V. Karlsen    schedule 03.02.2010
comment
Основной поток владеет значением блокировки lockSharedObject. Это имеет значение?   -  person Friedrich    schedule 03.02.2010
comment
О, я неправильно понял ваш комментарий. Interlock.Exchange является атомарным, так что да.   -  person Friedrich    schedule 03.02.2010
comment
Но Interlocked.Exchange возвращает значение 1-го аргумента до того, как ему было присвоено новое значение, поэтому вы перейдете к обработке цикла while только после того, как другой поток заявит флаг (и этот поток не сбросит флаг).   -  person Michael Burr    schedule 03.02.2010


Ответы (5)


Глядя на второй пример, как он работает, кажется, что если блокировка не была получена, вы собираетесь выйти, пропустив важную часть вашего кода.

Подход Mutex гораздо проще понять, и у меня не было проблем с производительностью.

person Mitchel Sellers    schedule 03.02.2010
comment
Вы правы, следующий код нужно разместить сразу после цикла while. Цикл while вызывает сон. // при необходимости обмениваем sharedObject на newObject // очень мало обработки здесь Interlocked.Exchange(ref usingSharedObject, 0); // свободная блокировка - person Friedrich; 03.02.2010

Ваша заблокированная операция неверна. Спин-блокировка обычно выглядит примерно так:

int sharedLock = 0;

void callback()
{

do {
 int existingState = Interlocked.CompareExchange(ref sharedLock, 1, 0);
 if (0 == existingState) {
  break;
 }
} while (true);

try
{
 // operate on the shared state
}
finally
{
 int existingState = Interlocked.Exchange(ref sharedLock, 0);
 Debug.Assert (1 == existingState);
}

}

Что касается причины использования одного или другого, это зависит в первую очередь от типа операций, выполняемых при удерживании блокировки. Очень короткие операции (короткое арифметическое сложение/вычитание, простое изменение флага состояния и т. д.) лучше подходят для спин-блокировки. Тяжелые операции (распределение, доступ к вводу-выводу) не могут выполняться при спин-блокировке, поэтому они должны выполняться под истинным мьютексом.

person Remus Rusanu    schedule 03.02.2010

На первый взгляд кажется, что ваши 2 примера могут быть не эквивалентны. Мне кажется, что ваше решение для опроса, использующее Interlocked.Exchange(), попадет в цикл обработки, в то время как что-то еще заявит «самодельный семафор» и пропустит замену sharedObject и newObject, если самодельный семафор заявлен. (Если я чего-то не понимаю, что вполне возможно).

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

Win32 добавил объект мьютекса со счетчиком оборотов, чтобы получить лучшее из обоих миров того, что вы пытаетесь сделать здесь (я думаю). Однако, насколько я знаю, он еще не был представлен в среде .NET.

person Michael Burr    schedule 03.02.2010
comment
Итак, как я уже сказал, сделать это правильно может быть сложно, даже (или особенно) за исключением более очевидных ошибок, подобных этой. Придерживайтесь примитивов синхронизации, предоставляемых платформой, если вы действительно не обнаружите, что они являются узким местом. Затем замените их — и очень осторожно. - person Michael Burr; 03.02.2010

Я бы выбрал маршрут Mutex(), пока вы не уверены, что вам нужно использовать что-то более экзотическое.

Кроме того, рассмотрите возможность использования монитора (или оператора блокировки С# - то же самое), если вас беспокоит вес. Блокировка/монитор работает лучше, чем мьютекс. Однако он не виден за пределами вашего процесса (но тогда вы не создаете именованный мьютекс, поэтому похоже, что вам не нужно, чтобы он был виден за пределами вашего процесса).

person JMarsch    schedule 03.02.2010

Если вы используете .Net 4, вы также можете взглянуть на новую структуру SpinLock.

person JMarsch    schedule 03.02.2010