#c# #.net #multithreading #design-patterns #.net-4.0
#c# #.net #многопоточность #шаблоны проектирования #.net-4.0
Вопрос:
Существует ли в .net конструкция или шаблон, который определяет сегмент кода, к которому могут обращаться несколько потоков, но блокирует, если какой-либо поток находится в каком-либо другом сегменте кода (и наоборот)? Например:
void SomeOperationA()
{
Block( B )
{
Segment1:
... only executes if no threads are executing in Segment2 ...
}
}
}
void SomeOperationB()
{
Block( A )
{
Segment2:
... only executes if no threads are executing in Segment1 ...
}
}
Редактировать
Несколько потоков должны иметь возможность доступа к Segment1 / Segment2 одновременно (одновременно «активен» только один сегмент. Если выполняется сегмент 1, другой поток должен иметь возможность выполнять Сегмент1, но не Сегмент2.
Правка 2
Учитывая все комментарии / ответы и мой реальный сценарий, я понимаю, что требовать, чтобы Segment2 был доступен несколькими потоками, немного безумно.
Комментарии:
1. могут ли несколько потоков выполнять Segment2 одновременно?
2. Звучит как рецепт голодания.
3. @SFun28: каково реальное применение этого?
4. Вот сценарий в реальных терминах: Segment2 сериализует ConcurrentDictionary на диск. Происходит время от времени. При сериализации мы должны убедиться, что словарь стабилен. Segment1 считывает и / или изменяет данные. Это потокобезопасно, поскольку ConcurrentDictionary потокобезопасен. Мы хотим высокую пропускную способность в Segment1, но мы хотим остановить показ при выполнении Segment2. Если мы попытаемся выполнить Segment2, мы хотим придержать строку, пока не закончим выполнение Segment1. надеюсь, это поможет.
5. Чтобы добавить к моему сценарию «реального мира» выше: вызывающий код не будет монополизировать сегмент 1 — вероятно, сегмент 1 будет сильно пострадать в течение некоторого периода времени, затем будет вызван сегмент 2 и так далее. Механизм создан только для мер безопасности.
Ответ №1:
Это довольно необычная модель защиты ресурсов от неправильного одновременного доступа. Сначала я бы подумал, нельзя ли перевести ваш вариант использования в эквивалентный сценарий, где вы можете использовать простые блокировки. Если бы вы могли предоставить более подробную информацию о том, зачем вам нужна такая схема блокировки, возможно, сообщество предложило бы другие идеи.
Чтобы ответить на ваш конкретный вопрос, ничто в .NET или даже Win32 напрямую не поддерживает эту модель блокировки, однако вы можете создать ее из других примитивов. Я бы рассмотрел возможность использования пары . ReaderWriterLockSlim
экземпляров для защиты каждого ресурса. Когда потоки входят в SegmentA, вы получаете блокировку чтения на запись для A и блокировку записи B … и наоборот для потоков, входящих в SegmentB. Это позволило бы нескольким потокам выполняться в каждом сегменте, но не одновременно
РЕДАКТИРОВАТЬ: Учитывая ваш ответ в комментариях к вашему вопросу, я более убежден, что вам нужно рассмотреть возможность использования модели блокировки чтения / записи. То, что вы ищете, — это способ защитить ресурс таким образом, чтобы, когда «авторы» выполняют работу (сериализуют словарь), ни читатели, ни другие авторы не могли войти, и когда «читатели» выполняют работу, они не блокируют друг друга, но блокируют всех других авторов. Это классический случай блокировок чтения / записи.
ПРАВКА 2: Теперь, когда у меня появилось больше времени, я думаю, стоит остановиться на одном моменте. Способ думать о блокировках заключается в том, что они защищают ресурсы данных (память, файлы и т.д.), А не области кода. Тот факт, что нам нужно определить критические разделы кода, в которые одновременно может входить только один поток, является деталью реализации, которую не следует путать с тем, как используются общие ресурсы (и которые должны быть защищены). В вашем вопросе акцент на том, как контролировать, какие потоки могут входить в какой раздел кода, отвлекает от реальной проблемы: какие ресурсы данных вы пытаетесь защитить от какого рода изменений. Как только вы посмотрите на проблему с этой точки зрения, становится яснее, какие парадигмы реализации имеют смысл.
Вот несколько хороших ресурсов по моделям блокировки чтения / записи:
http://msdn.microsoft.com/en-us/magazine/cc163599.aspx
http://msdn.microsoft.com/en-us/library/bz6sth95.aspx
http://blogs.msdn.com/b/vancem/archive/2006/03/29/564854.aspx
Комментарии:
1. Я думаю, что необычным это делает многопоточный доступ к Segment2. Если мы скажем, что только один поток может одновременно обращаться к Segment2, но при этом выполнять все остальные требования, я думаю, это просто сводится к ограничению ReaderWriterLockSlim, правильно?
2. Это не будет работать с
ReaderWriterLockSlim
и вот почему. Вы не можете ввести блокировку записи одновременно. Это приведет к взаимному исключению, и в конечном итоге только один поток сможет выполнить SegmentA3. Пара ReaderWriterLocks, как описано, не работает. Как только вы перехватываете блокировку записи, вы препятствуете одновременному выполнению этого блока двумя потоками…
4. @SFun28: Да, я считаю, что это правильно. Тем не менее, я бы рекомендовал такого рода сокращение, поскольку оно выводит вас на известную территорию для использования блокировки … а не в глуши.
5. @Reed Copsey: Да, я полагаю, вы правы. Я обновлю свой ответ.
Ответ №2:
Учитывая вашу правку, похоже, что правильным подходом было бы использовать ReaderWriterLockSlim, поскольку вам действительно не следует изменять коллекцию при запуске Segment2, и вы не должны разрешать запускать более 1 Segment2:
private static ReaderWriterLockSlim readerLock = new ReaderWriterLockSlim();
void SomeOperationA()
{
try
{
readerLock.EnterReadLock();
// Segment1:
// ... only executes if no threads are executing in Segment2 ...
}
finally
{
readerLock.ExitReadLock();
}
}
void SomeOperationB()
{
try
{
readerLock.EnterWriteLock();
// Prevents multiple Segment2 from serializing, and prevents all Segment1 threads...
}
finally
{
readerLock.ExitWriteLock();
}
}
Комментарии:
1. Он не сказал, что Segment2 не может выполняться одновременно. Простой
lock
не поможет, если это правда.2. @Andrey: Да — я отредактировал, чтобы включить это — если это требование, все становится немного сложнее…
3. извините, я хотел включить это. Да, два потока должны иметь возможность выполнять Segment1 / 2 одновременно (но не оба сегмента одновременно!)
4.
SomeOperationB()
не может выполняться одновременно. Они должны работать одинаково.5. @Andrey: Учитывая комментарии OPs о цели здесь, я не верю, что это правда…
Ответ №3:
Что-то вроде этого.
class Segments
{
public const int None = 0;
public const int Segm1 = 1;
public const int Segm2 = 2;
}
int currentSegm = 0;
int segm1counter = 0;
int segm2counter = 0;
object segm1lock = new object();
object segm2lock = new object();
void SomeOperationA()
{
while (
Interlocked.CompareExchange(ref currentSegm, Segments.Segm1, Segments.Segm1) != Segments.Segm1
amp;amp;
Interlocked.CompareExchange(ref currentSegm, Segments.Segm1, Segments.None) != Segments.None
)
{
Thread.Yield();
}
Interlocked.Increment(ref segm1counter);
try
{
//Segment1:
//... only executes if no threads are executing in Segment2 ...
}
finally
{
lock (segm1lock)
{
if (Interlocked.Decrement(ref segm1counter) == 0)
currentSegm = Segments.None;
}
}
}
void SomeOperationB()
{
while (
Interlocked.CompareExchange(ref currentSegm, Segments.Segm2, Segments.Segm2) != Segments.Segm2
amp;amp;
Interlocked.CompareExchange(ref currentSegm, Segments.Segm2, Segments.None) != Segments.None
)
{
Thread.Yield();
}
Interlocked.Increment(ref segm2counter);
try
{
//Segment2:
//... only executes if no threads are executing in Segment2 ...
}
finally
{
lock (segm2lock)
{
if (Interlocked.Decrement(ref segm2counter) == 0)
currentSegm = Segments.None;
}
}
}
Хорошо, это не будет работать с блокировкой ReaderWriter/
Комментарии:
1. У меня было что-то вроде этого — но блокировки записи предотвращают 2 одновременных потока, что эффективно превращает это в один вызов «блокировки».
2. Большое спасибо за кодирование решения к моему первоначальному сообщению. Теперь я понимаю, что мой первоначальный сценарий довольно сумасшедший.
3. @SFun28 это интересная головоломка. Я не очень вникал в первопричину.