Блокировка включена, и дальнейшие попытки блокировки не блокируются: блокировки C # повторно вводятся? - PullRequest
20 голосов
/ 31 января 2011

Я написал тест того, что, по моему мнению, должно быть обоснованным для тупика.Похоже, что после того, как 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 без тупиков!

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

Ответы [ 3 ]

42 голосов
/ 31 января 2011

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

13 голосов
/ 31 января 2011

Классы 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. Он не может работать, пока поток пользовательского интерфейса не будет свободен, свойство Busy BGW не будет ложным, пока не завершится работа обработчика событий.

1 голос
/ 31 января 2011

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

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

...