Как мне заменить этот семафор на монитор?

В предыдущем моем вопросе кто-то имел в виду, что использование семафоров в C # дорого обходится по сравнению с использованием монитора. Итак, я спрашиваю, как мне заменить семафор в этом коде на монитор?

Мне нужно, чтобы функция 1 возвращала свое значение после завершения функции 2 (в отдельном потоке). Я заменил Semaphore.WaitOne на Monitor.Wait и Semaphore.Release на Monitor.PulseAll, но PulseAll запускался до того, как Wait вызывал зависание программы. Есть идеи, как избежать этого состояния гонки?

Semaphore semaphore = new Semaphore(0,1);
byte b;
public byte Function1()
{
    // new thread starting in Function2;

    semaphore.WaitOne();
    return b;
}

public void Function2()
{
    // do some thing
    b = 0;
    semaphore.Release();
}

person Kurru    schedule 02.04.2010    source источник


Ответы (2)


Вы можете сделать это с помощью WaitHandle вместо семафора. Это была бы самая простая альтернатива и работала бы лучше, чем семафор:

ManualResetEvent manualResetEvent = new ManualResetEvent(false);
byte b;
public byte Function1()
{
    // new thread starting in Function2;

    manualResetEvent.WaitOne();
    return b;
}

public void Function2()
{
    // do some thing
    b = 0;
    manualResetEvent.Set();
}
person Reed Copsey    schedule 02.04.2010
comment
Чтобы объяснить этот ответ другим читателям - Semaphore необходимо только тогда, когда вам нужно разрешить некоторому фиксированному числу потоков ›1 для получения общего ресурса. Он похож на Monitor, но также не является правильной семантикой для исходного вопроса, который пытается решить проблему ожидания, пока эта операция не будет выполнена. Для этой семантики дескриптор ожидания (ManualResetEvent/AutoResetEvent обычно более подходит, чем критический раздел (_4 _ / _ 5_). - person Aaronaught; 03.04.2010
comment
@Aaronaught: Очень хорошее объяснение деталей того, почему в данном случае это лучший подход. - person Reed Copsey; 03.04.2010
comment
@Aaronaught, @ Reed: боже. Я только что удалил часть одного из своих других ответов на том основании, что SemaphoreWaitHandle в целом) более тяжелый, чем Monitor. Я считаю, что это семантически более правильно (в этом конкретном случае лучше всего подходит семафор), но должен ли я, например, развернуть правку и оставить там пример семафора, потому что он семантически более правильный? Просто интересно ... Есть мысли? - person Andras Vass; 03.04.2010
comment
@andras: Лично я выбираю вариант, который лучше всего соответствует задаче. В этом случае подходящим вариантом является WaitHandle. Накладные расходы на все дополнительные вещи, необходимые для использования монитора и его правильной работы, перевешивают прирост производительности. Семафор в данном случае не совсем правильный, поскольку блокируется только один поток. - person Reed Copsey; 03.04.2010
comment
@ Рид: Я не могу согласиться с этим, однако в этом вопросе есть несколько пробелов. 1. заголовок серьезно противоречит образцу кода. Если я возьму заголовок, OP нужны семафоры. Если я возьму пример кода, ему нужны события. 2. мы не знаем, действительно ли OP нуждается в высокой производительности или просто страдает POS (синдром преждевременной оптимизации). Я подозреваю последнее. С учетом сказанного, если вопрос действительно в том, о чем говорится в названии, я просто не мог удержаться от ответа. Моя вина. :П - person Andras Vass; 03.04.2010
comment
@andras: Semaphore - это своего рода странный класс, у него есть синтаксис мьютекса, но семантика критического раздела, поэтому его вообще не следует использовать, за исключением случаев, когда действительно необходим множественный, но ограниченный параллелизм (т.е. редко). Что касается ManualResetEvent, это лучший выбор с точки зрения дизайна, чем спин-блокировка на основе Monitor, если это та семантика, которую вы хотите - а классы Slim в .NET 4 должны избавить от проблем с производительностью (не то чтобы я когда-либо замечал замедление перед этим). Возможно, я что-то упускаю, но я почти уверен, что это важный момент. - person Aaronaught; 03.04.2010
comment
@Reed, @Aaronaught: спасибо, что указали на важность этого. Я соответственно обновил ответ. (Я верю. :) @Aaronaught: Я считаю, что Монитор - странный зверь. Я не совсем понимаю, что вы имеете в виду под спин-блокировкой на основе Monitor. Верно, что он может вращаться, но делает это разумно. Он также обнаруживает конфликты и при необходимости раздувает блокировку до плоской блокировки и т. Д. Поскольку это первичный примитив синхронизации, я думаю, над этим было много размышлений. ManualResetEventSlim в .NET 4 часто его используют ....: P - person Andras Vass; 03.04.2010
comment
@Aaronaught: К вашему сведению, файлы семафоров :) stackoverflow.com/questions/2528907/ (Возможно, мне также стоит изменить это на счетчик, основанный на событиях ....) (О, и плоско lock = жирный замок в вышеприведенном.): P - person Andras Vass; 03.04.2010
comment
@Aaronaught: Хммм. После небольшого перерыва я пришел к выводу, что семафоры (или замки со счетчиками) в некоторых случаях в конце концов намного удобнее. : P (То есть я думаю, что мне было бы довольно сложно ответить на вышеуказанный вопрос с помощью auto / manualresetevents.) - person Andras Vass; 03.04.2010
comment
@Andras: Я предпочитаю использовать ManualResetEvent (вместе со счетчиком), чтобы ответить на вышеуказанный вопрос. Я добавлю туда ответ, чтобы продемонстрировать. - person Reed Copsey; 03.04.2010
comment
@andras: Я использую тот же подход, что и Рид, за исключением использования ThreadPool вместо бесплатных потоков. На самом деле я увидел другой вопрос и оставил его в покое, потому что я всегда использовал один и тот же фрагмент кода и решил, что дам кому-нибудь еще шанс (например, Риду). : P Сказав это, вы могли использовать Semaphore в качестве защелки обратного отсчета для ленивого человека, но мне просто не нравится, как он выглядит, а Interlocked по сути не блокируется. - person Aaronaught; 03.04.2010
comment
@Aaronaught: Обычно я делаю это и через ThreadPool, хотя это в некоторой степени зависит от выполняемой работы. :) - person Reed Copsey; 03.04.2010
comment
@Aaronaught, @Reed: Думаю, я все время был виноват в том, что ленился. Это никогда не казалось правильным, но я все равно использовал его. (Мне это не нужно было так часто, и, похоже, это не имело значения с точки зрения производительности.) Теперь это кажется таким ясным. d: -o ‹= да, я искал это. Это был смайлик, чтобы снять с тебя шляпу. :) - person Andras Vass; 06.04.2010

@Reed предоставил элегантное решение, если вам нужно дождаться нескольких потоков.

Возможно, вы не захотите использовать Monitor для этого. Как указано в @Reed, события будет достаточно, и оно предоставит наиболее чистое и понятное решение, соответствующее требованиям вашего кода.
Накладные расходы на использование реальных примитивов синхронизации операционной системы, скорее всего, не будут иметь значения в вашем случае и при использовании, например, Monitor обеспечит лишь убывающую отдачу за счет гораздо большей сложности.

С учетом сказанного, вот реализация, использующая Monitor и сигнализацию.

Вы можете использовать флаг bool, охраняемый блокировкой, чтобы указать, что вы закончили, и избежать ожидания в этом случае. (A)
Если вы действительно начинаете новый поток в Function2(), где в комментариях указывается и используете lock() вокруг обоих WaitOne() и Release(), вам вообще не нужен флаг. (В)

A, используя флаг:

class Program
{
    static object syncRoot = new object();
    //lock implies a membar, no need for volatile here.
    static bool finished = false;
    static byte b;

    public static byte Function1()
    {
        lock (syncRoot)
        {
            //Wait only if F2 has not finished yet.
            if (!finished)
            {
                Monitor.Wait(syncRoot);
            }
        }
        return b;
    }

    static public void Function2()
    {
        // do some thing
        b = 1;
        lock (syncRoot)
        {
            finished = true;
            Monitor.Pulse(syncRoot);
        }
    }

    static void Main(string[] args)
    {
        new Thread(Function2).Start();
        Console.WriteLine(Function1());
    }
}

B, начиная с Function1:

class Program
{

    static object syncRoot = new object();
    static byte b;

    public static byte Function1()
    {
        lock (syncRoot)
        {
            // new thread starting in Function2;
            new Thread(Function2).Start();
            Monitor.Wait(syncRoot);
        }
        return b;
    }

    static public void Function2()
    {
        // do some thing
        b = 1;
        //We need to take the lock here as well
        lock (syncRoot)
        {
            Monitor.Pulse(syncRoot);
        }
    }

    static void Main(string[] args)
    {
        Console.WriteLine(Function1());
    }
}
person Andras Vass    schedule 02.04.2010