Подтвердить что ты не робот

Блокировка aqcuired и дальнейшие попытки блокировки не блокируются: перезаписываются ли блокировки С#?

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

Вот класс:

internal class Tester
{
    private readonly object _sync = new object();

    public Tester() { }

    public void TestLock()
    {
        lock (_sync)
        {
            for (int i = 0; i < 10; i++)
            {
                Deadlock(i);
            }
        }

    }

    private void Deadlock(int i)
    {
        lock (_sync)
        {
            Trace.WriteLine(i + " no deadlock!");
        }
    }
}

Вывод:

0 нет тупика!
1 нет тупика!
2 без тупика!
3 нет тупика!
4 нет тупика!
5 нет тупика!
6 нет тупика!
7 нет тупика!
8 нет тупика!
9 нет тупика!

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

4b9b3361

Ответ 1

Замки в .NET являются реентерабельными. Блокируются только приобретения из других потоков. Когда один и тот же поток блокирует один и тот же объект несколько раз, он просто увеличивает счетчик и уменьшает его при отпускании. Когда счетчик достигает нуля, блокировка фактически освобождается для доступа из других потоков.

Ответ 2

Классы Monitor, Mutex и ReaderWriterLock поддерживают блокировки, которые имеют сходство потоков. Класс ReaderWriterLockSlim позволяет вам выбирать, у него есть конструктор, который принимает значение LockRecursionPolicy. Использование LockRecursionPolicy.NoRecursion - это оптимизация, довольно большая, если ваша блокировка действительно мелкозернистая.

Класс Semaphore - это класс синхронизации, который не имеет сходства нитей. Этот код надежно блокирует:

class Tester {
    private Semaphore sem = new Semaphore(1, 1);
    public void TestLock() {
        sem.WaitOne();
        for (int i = 0; i < 10; i++) Deadlock(i);
        sem.Release();
    }

    private void Deadlock(int i) {
        if (!sem.WaitOne(100)) Console.WriteLine("deadlock!");
        else {
            sem.Release();
            Console.WriteLine("No deadlock!");
        }
    }
}

В общем, классы аффинной синхронизации потока требуют двух потоков и двух блокировок в тупик. Стандартный шаблон предназначен для того, чтобы один поток мог получить блокировки A и B, а другой - для B и A. Важен порядок.

В .NET-программировании существуют менее очевидные сценарии взаимоблокировок, вызванные блокировками, которые вы не видите, поскольку они встроены в код платформы .NET. Очень классический для BackgroundWorker. Вы можете написать код в потоке пользовательского интерфейса, который вращается в свойстве Busy, ожидая завершения работы BGW. Это всегда взаимоблокирует, когда BGW имеет обработчик события RunWorkerCompleted. Он не может работать до тех пор, пока поток пользовательского интерфейса не будет работать, свойство BGW Busy не будет false до тех пор, пока обработчик события не закончит работу.

Ответ 3

В вашем сценарии у вас есть блокировка в другой блокировке. Когда код попадает в гнездовую блокировку в "Deadlock", код "lock (...)" по существу игнорируется, поскольку он уже приобрел его в "TestLock".

Отличный источник для потоковой передачи: http://www.albahari.com/threading/part2.aspx.