блокировка против мьютекса, проблемы масштабирования - PullRequest
5 голосов
/ 03 февраля 2010

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

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

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

1. Решение Mutex:

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 хорошо работает в этой области.

С уважением,

Friedrich

Ответы [ 5 ]

5 голосов
/ 03 февраля 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);
}

}

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

3 голосов
/ 03 февраля 2010

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

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

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

2 голосов
/ 03 февраля 2010

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

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

1 голос
/ 03 февраля 2010

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

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

0 голосов
/ 03 февраля 2010

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

...