Здравствуйте, adontz, Вы писали:
A>Здравствуйте, sergunok, Вы писали:
A>Поведение сабжа не ясно. Можете привести сценарий использования, а лучше задачу где вам такое надо?
что-то типа:
// нити типа А
megalock.Lock(5)
...
megalock.Unlock()
// нити типа Б
megalock.Lock(3)
...
megalock.Unlock()
Если к примеру сейчас критическую секцию захватила нить типа А и ломятся еще и другие нити разных типов, в первую очередь будут обслуживаться нити типа (т.к. их приоритет выше)
Здравствуйте, Aen Sidhe, Вы писали:
AS>Здравствуйте, sergunok, Вы писали:
S>>Такое есть в современном .NET?
S>>Или нужно реализовывать самому?
AS>lock — это обычный монитор. Какой ещё "монитор с приоритетом"?
Пусть монитор, меня интересует критическая секция с возможностью приоритезации.. Чем реализуется — семафором или монитором — не важно.
Здравствуйте, sergunok, Вы писали:
S>Если к примеру сейчас критическую секцию захватила нить типа А и ломятся еще и другие нити разных типов, в первую очередь будут обслуживаться нити типа (т.к. их приоритет выше)
Здравствуйте, adontz, Вы писали:
A>Здравствуйте, sergunok, Вы писали:
S>>Если к примеру сейчас критическую секцию захватила нить типа А и ломятся еще и другие нити разных типов, в первую очередь будут обслуживаться нити типа (т.к. их приоритет выше)
A>А почему нельзя задать нитям приоритет?
A>http://msdn.microsoft.com/en-us/library/system.threading.thread.priority.aspx
Здравствуйте, sergunok, Вы писали:
S>Если к примеру сейчас критическую секцию захватила нить типа А и ломятся еще и другие нити разных типов, в первую очередь будут обслуживаться нити типа (т.к. их приоритет выше)
Если секция уже захвачена — то как другие её захватят? Куда денется поток А?
Здравствуйте, fddima, Вы писали:
S>>Если к примеру сейчас критическую секцию захватила нить типа А и ломятся еще и другие нити разных типов, в первую очередь будут обслуживаться нити типа (т.к. их приоритет выше) F> Если секция уже захвачена — то как другие её захватят? Куда денется поток А?
Имеется в виду, что после освобождения секции потоком А управление передастся тому потоку, кто передал большее значение в метод Lock.
Здравствуйте, sergunok, Вы писали:
A>>А почему нельзя задать нитям приоритет?
S>Хочется более нативный способ
Вы о чём? Куда ещё нативней? Lock работает в Ring3, пока не нарвётся на уже залоченнный кем-то объект, после этого управление передаётся ядру. Когда объект освободится, уже ядро принимает решение какому из ожидающих потоков его отдать. Алгоритм этого выбора не документирован и по факту разный в разных ОС. Единственный способ повлиять на этот выбор из ring3 — установка приоритета потоков.
Слепить такой механизм в ring3 конечно можно, но боюсь, он получится громоздкий и неповоротливый, сомневаюсь в целесобразности его использования.
Здравствуйте, sergunok, Вы писали:
S>что-то типа: S>// нити типа А S>megalock.Lock(5); ... КодА ... megalock.Unlock(); S>// нити типа Б S>megalock.Lock(3); ... КодБ ... megalock.Unlock(); S>Если к примеру сейчас критическую секцию захватила нить типа А и ломятся еще и другие нити разных типов, в первую очередь будут обслуживаться нити типа (т.к. их приоритет выше)
Давай порассуждаем:
Если захвачена секция A, значит код этой секции выполняется (представим, что это происходит достаточно плавно — очень мелкими тактами).
Через N времени поток Б дошёл до точки блокировки, которая имеет, допустим, больший приоритет.
Вопрос: в какой части кода (КодА), между Lock Unlock, поток А должен встать в ожидание?
На этот вопрос можно ответить, только если КодА будет мелко прорежен проверками чтобы встать на ожидание.
Получится чрезвычайно затратный оверхедный код.
А вот ситуация, когда несколько потоков подходят одновременно к точке блокировки — практически равна нулю.
Поэтому, чтобы избежать гонки за ресурсы и правильно распределить вычислительные нагрузки, можно воспользоваться
другой техникой:
Разбивать код на более мелкие секции, но при этом пользоваться классом SpinLock вместо lock.
Опять таки это зависит от конкретной задачи и не факт, что это будет работать оптимальнее.
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, sergunok, Вы писали:
A>>>А почему нельзя задать нитям приоритет?
S>>Хочется более нативный способ
JR>Вы о чём? Куда ещё нативней? Lock работает в Ring3, пока не нарвётся на уже залоченнный кем-то объект, после этого управление передаётся ядру. Когда объект освободится, уже ядро принимает решение какому из ожидающих потоков его отдать. Алгоритм этого выбора не документирован и по факту разный в разных ОС. Единственный способ повлиять на этот выбор из ring3 — установка приоритета потоков.
JR>Слепить такой механизм в ring3 конечно можно, но боюсь, он получится громоздкий и неповоротливый, сомневаюсь в целесобразности его использования.
Приоритет повлияет на планирование выполнения нитей.
Здравствуйте, Jolly Roger, Вы писали:
JR>Здравствуйте, sergunok, Вы писали:
S>>Приоритет повлияет на планирование выполнения нитей.
JR>Разумеется, но разве Вам не это по сути надо? Может Вы всё-таки расскажете, зачем это Вам понадобилось?
Влиять на распределение процессорного времени между нитями в качестве побочного эффекта мне нужно.
Все, что нужно, — в начале критической секции выстраивать очередь на попадание в оную согласно явно заданным приоритетам.
Здравствуйте, sergunok, Вы писали: S>Влиять на распределение процессорного времени между нитями в качестве побочного эффекта мне нужно. S>Все, что нужно, — в начале критической секции выстраивать очередь на попадание в оную согласно явно заданным приоритетам.
Что Вам нужно-не нкжно, я понял Я спрашивал зачем. Впрочем, как знаете.
Вот, набросал (даже запускать не пробовал), чтобы продемонстрировать возможный подход, посмотрите
Скрытый текст
#pragma warning disable 420
class PriorityLock: IDisposable
{
public static readonly int MaxPriority = 15;
class LockFreeStack<T> where T : class
{
class StackItem<T1> where T1 : class
{
public T1 Value;
public volatile StackItem<T1> Next = null;
public StackItem(T1 data) { Value = data; }
}
private volatile StackItem<T> Head = null;
public T Pop()
{
StackItem<T> tmpHead = Head;
StackItem<T> tmpHead2;
for (; ; )
{
if (tmpHead == null) return null;
tmpHead2 = Interlocked.CompareExchange<StackItem<T>>(
ref Head, tmpHead.Next, tmpHead);
if (tmpHead2 == tmpHead)
return tmpHead.Value;
tmpHead = tmpHead2;
}
}
public void Push(T data)
{
StackItem<T> item = new StackItem<T>(data);
StackItem<T> tmpHead = Head;
StackItem<T> tmpHead2;
for (; ; )
{
item.Next = tmpHead;
tmpHead2 = Interlocked.CompareExchange<StackItem<T>>(
ref Head, item, tmpHead);
if (tmpHead2 == tmpHead)
return;
tmpHead = tmpHead2;
}
}
}
struct Queueitem
{
public int ThreadID;
public EventWaitHandle handle;
}
volatile int disposed = 0;
volatile int lockOwner;
int maxPriority;
object innerLock = new object();
Stack<Queueitem>[] queues;
LockFreeStack<EventWaitHandle> freeEvents;
public PriorityLock(int MaxPriorityLevel)
{
if (MaxPriorityLevel < 0 || MaxPriorityLevel > MaxPriority)
throw new ArgumentException("", "MaxPriorityLevel");
maxPriority = MaxPriorityLevel;
freeEvents = new LockFreeStack<EventWaitHandle>();
queues = new Stack<Queueitem>[maxPriority + 1];
for (int i = 0; i <= maxPriority; i++)
{
queues[i] = new Stack<Queueitem>(16);
}
}
public void Enter(int Level)
{
if (disposed != 0) throw new ObjectDisposedException("PriorityLock");
try { }
finally
{
if (Level < 0 || Level > maxPriority)
throw new ArgumentException("", "Level");
var currentId = Thread.CurrentThread.ManagedThreadId;
var currentOwner =
Interlocked.CompareExchange(ref lockOwner, currentId, 0);
if (currentOwner != 0)
{
if (currentOwner == currentId)
throw new InvalidOperationException("Method isn't re-enterant");
EventWaitHandle ev = freeEvents.Pop();
if (ev == null)
ev = new EventWaitHandle(false, EventResetMode.AutoReset);
lock (innerLock)
{
var item = new Queueitem();
item.ThreadID = currentId;
item.handle = ev;
queues[Level].Push(item);
}
ev.WaitOne();
freeEvents.Push(ev);
}
}
}
public void Leave()
{
if (disposed != 0) throw new ObjectDisposedException("PriorityLock");
var ownerId = lockOwner;
var currentId = Thread.CurrentThread.ManagedThreadId;
if (ownerId != currentId)
throw new InvalidOperationException(
"Current thread doesn't own the given object");
try { }
finally
{
int newOwner = 0;
lock (innerLock)
{
for (int i = maxPriority; i >= 0; i--)
{
if (queues[i].Count > 0)
{
var item = queues[i].Pop();
newOwner = item.ThreadID;
item.handle.Set();
break;
}
}
lockOwner = newOwner;
}
}
}
public void Dispose()
{
var d = Interlocked.Exchange(ref disposed, 1);
if (d != 0) throw new ObjectDisposedException("PriorityLock");
lock (innerLock)
{
for (int i = 0; i <= maxPriority; i++)
{
foreach(var item in queues[i])
{
item.handle.Close();
}
queues[i].Clear();
}
do
{
var ev = freeEvents.Pop();
if (ev != null) ev.Close();
else break;
} while (true);
}
}
}
PS. В плане "зачем" — иначе и не скажешь, как приоритезация входа в крит.секцию.
Здравствуйте, Jolly Roger, Вы писали: JR>Здравствуйте, sergunok, Вы писали: S>>Влиять на распределение процессорного времени между нитями в качестве побочного эффекта мне нужно. S>>Все, что нужно, — в начале критической секции выстраивать очередь на попадание в оную согласно явно заданным приоритетам. JR>Что Вам нужно-не нкжно, я понял Я спрашивал зачем. Впрочем, как знаете. JR>Вот, набросал (даже запускать не пробовал), чтобы продемонстрировать возможный подход, посмотрите JR>
Скрытый текст
JR> #pragma warning disable 420
JR> class PriorityLock: IDisposable
JR> {
JR> public static readonly int MaxPriority = 15;
JR> class LockFreeStack<T> where T : class
JR> {
JR> class StackItem<T1> where T1 : class
JR> {
JR> public T1 Value;
JR> public volatile StackItem<T1> Next = null;
JR> public StackItem(T1 data) { Value = data; }
JR> }
JR> private volatile StackItem<T> Head = null;
JR> public T Pop()
JR> {
JR> StackItem<T> tmpHead = Head;
JR> StackItem<T> tmpHead2;
JR> for (; ; )
JR> {
JR> if (tmpHead == null) return null;
JR> tmpHead2 = Interlocked.CompareExchange<StackItem<T>>(
JR> ref Head, tmpHead.Next, tmpHead);
JR> if (tmpHead2 == tmpHead)
JR> return tmpHead.Value;
JR> tmpHead = tmpHead2;
JR> }
JR> }
JR> public void Push(T data)
JR> {
JR> StackItem<T> item = new StackItem<T>(data);
JR> StackItem<T> tmpHead = Head;
JR> StackItem<T> tmpHead2;
JR> for (; ; )
JR> {
JR> item.Next = tmpHead;
JR> tmpHead2 = Interlocked.CompareExchange<StackItem<T>>(
JR> ref Head, item, tmpHead);
JR> if (tmpHead2 == tmpHead)
JR> return;
JR> tmpHead = tmpHead2;
JR> }
JR> }
JR> }
JR> struct Queueitem
JR> {
JR> public int ThreadID;
JR> public EventWaitHandle handle;
JR> }
JR> volatile int disposed = 0;
JR> volatile int lockOwner;
JR> int maxPriority;
JR> object innerLock = new object();
JR> Stack<Queueitem>[] queues;
JR> LockFreeStack<EventWaitHandle> freeEvents;
JR> public PriorityLock(int MaxPriorityLevel)
JR> {
JR> if (MaxPriorityLevel < 0 || MaxPriorityLevel > MaxPriority)
JR> throw new ArgumentException("", "MaxPriorityLevel");
JR> maxPriority = MaxPriorityLevel;
JR> freeEvents = new LockFreeStack<EventWaitHandle>();
JR> queues = new Stack<Queueitem>[maxPriority + 1];
JR> for (int i = 0; i <= maxPriority; i++)
JR> {
JR> queues[i] = new Stack<Queueitem>(16);
JR> }
JR> }
JR> public void Enter(int Level)
JR> {
JR> if (disposed != 0) throw new ObjectDisposedException("PriorityLock");
JR> try { }
JR> finally
JR> {
JR> if (Level < 0 || Level > maxPriority)
JR> throw new ArgumentException("", "Level");
JR> var currentId = Thread.CurrentThread.ManagedThreadId;
JR> var currentOwner =
JR> Interlocked.CompareExchange(ref lockOwner, currentId, 0);
JR> if (currentOwner != 0)
JR> {
JR> if (currentOwner == currentId)
JR> throw new InvalidOperationException("Method isn't re-enterant");
JR> EventWaitHandle ev = freeEvents.Pop();
JR> if (ev == null)
JR> ev = new EventWaitHandle(false, EventResetMode.AutoReset);
JR> lock (innerLock)
JR> {
JR> var item = new Queueitem();
JR> item.ThreadID = currentId;
JR> item.handle = ev;
JR> queues[Level].Push(item);
JR> }
JR> ev.WaitOne();
JR> freeEvents.Push(ev);
JR> }
JR> }
JR> }
JR> public void Leave()
JR> {
JR> if (disposed != 0) throw new ObjectDisposedException("PriorityLock");
JR> var ownerId = lockOwner;
JR> var currentId = Thread.CurrentThread.ManagedThreadId;
JR> if (ownerId != currentId)
JR> throw new InvalidOperationException(
JR> "Current thread doesn't own the given object");
JR> try { }
JR> finally
JR> {
JR> int newOwner = 0;
JR> lock (innerLock)
JR> {
JR> for (int i = maxPriority; i >= 0; i--)
JR> {
JR> if (queues[i].Count > 0)
JR> {
JR> var item = queues[i].Pop();
JR> newOwner = item.ThreadID;
JR> item.handle.Set();
JR> break;
JR> }
JR> }
JR> lockOwner = newOwner;
JR> }
JR> }
JR> }
JR> public void Dispose()
JR> {
JR> var d = Interlocked.Exchange(ref disposed, 1);
JR> if (d != 0) throw new ObjectDisposedException("PriorityLock");
JR> lock (innerLock)
JR> {
JR> for (int i = 0; i <= maxPriority; i++)
JR> {
JR> foreach(var item in queues[i])
JR> {
JR> item.handle.Close();
JR> }
JR> queues[i].Clear();
JR> }
JR> do
JR> {
JR> var ev = freeEvents.Pop();
JR> if (ev != null) ev.Close();
JR> else break;
JR> } while (true);
JR> }
JR> }
JR> }
Не очень понял как использование спинлоков поможет приоритизировать попадание в крит.секцию.
Здравствуйте, XRonos, Вы писали:
XR>Здравствуйте, sergunok, Вы писали:
S>>что-то типа: S>>// нити типа А S>>megalock.Lock(5); ... КодА ... megalock.Unlock(); S>>// нити типа Б S>>megalock.Lock(3); ... КодБ ... megalock.Unlock(); S>>Если к примеру сейчас критическую секцию захватила нить типа А и ломятся еще и другие нити разных типов, в первую очередь будут обслуживаться нити типа (т.к. их приоритет выше)
XR>Давай порассуждаем: XR>Если захвачена секция A, значит код этой секции выполняется (представим, что это происходит достаточно плавно — очень мелкими тактами). XR>Через N времени поток Б дошёл до точки блокировки, которая имеет, допустим, больший приоритет. XR>Вопрос: в какой части кода (КодА), между Lock Unlock, поток А должен встать в ожидание? XR>На этот вопрос можно ответить, только если КодА будет мелко прорежен проверками чтобы встать на ожидание. XR>Получится чрезвычайно затратный оверхедный код.
XR>А вот ситуация, когда несколько потоков подходят одновременно к точке блокировки — практически равна нулю.
XR>Поэтому, чтобы избежать гонки за ресурсы и правильно распределить вычислительные нагрузки, можно воспользоваться XR>другой техникой: XR>Разбивать код на более мелкие секции, но при этом пользоваться классом SpinLock вместо lock. XR>Опять таки это зависит от конкретной задачи и не факт, что это будет работать оптимальнее.
Только не забывайте, что это всего лишь набросок, который мной не то что не тестировался — даже не запускался, да и не анализировался. Будьте осторожны.