Почему перемещение этой линии вызывает тупик? - PullRequest
0 голосов
/ 06 ноября 2018

Я думаю, что мне здесь не хватает чего-то очевидного:

Почему этот код не вызывает тупик:

static void Main(string[] args)
{
    object _lock1 = new object();
    object _lock2 = new object();

    Thread code1 = new Thread(() =>
    {
        lock (_lock1)
        {
            lock (_lock2)
            {
                Console.WriteLine("A");
                Thread.Sleep(3000);
            }
        }
    });

    Thread code2 = new Thread(() =>
    {
        lock (_lock2)
        {
            lock (_lock1)
            {
                Console.WriteLine("B");
                Thread.Sleep(3000);
            }
        }
    });

    code1.Start();
    code2.Start();

    code1.Join();
    code2.Join();

    Console.WriteLine("Done");
}

Но этот делает:

static void Main(string[] args)
{
    object _lock1 = new object();
    object _lock2 = new object();

    Thread code1 = new Thread(() =>
    {
        lock (_lock1)
        {
            lock (_lock2)
            {
                Thread.Sleep(3000);
                Console.WriteLine("A");
            }
        }
    });

    Thread code2 = new Thread(() =>
    {
        lock (_lock2)
        {
            Thread.Sleep(3000);
            lock (_lock1)
            {
                Console.WriteLine("B");
            }
        }
    });

    code1.Start();
    code2.Start();

    code1.Join();
    code2.Join();

    Console.WriteLine("Done");
}

Ответы [ 3 ]

0 голосов
/ 06 ноября 2018

Оба фрагмента кода могут вызвать взаимные блокировки, и их следует избегать. Это просто совпадение, что первый фрагмент не зашел в тупик. Добавление некоторой операции между блокировками увеличивает вероятность получить мертвую блокировку. Например, если вы добавляете Console.Writeline между lock1 и lock2, это также увеличивает вероятность тупика. Вы можете запустить свой первый фрагмент в цикле и получить тупик. Например, этот код через некоторое время попадает в тупик:

        static void Main(string[] args)
        {
            for (int i = 0; i < 1000; i++)
            {
                object _lock1 = new object();
                object _lock2 = new object();

                Thread code1 = new Thread(() =>
                {
                    lock (_lock1)
                    {
                        lock (_lock2)
                        {
                            Console.WriteLine("A");
                            Thread.Sleep(100);
                        }
                    }
                });

                Thread code2 = new Thread(() =>
                {
                    lock (_lock2)
                    {
                        lock (_lock1)
                        {
                            Console.WriteLine("B");
                            Thread.Sleep(100);
                        }
                    }
                });

                code1.Start();
                code2.Start();

                code1.Join();
                code2.Join();
            }
            Console.WriteLine("Done");
        }
0 голосов
/ 06 ноября 2018

Обычный поток, «предполагая», что поток 1 выполняется первым (на самом деле он все еще может зайти в тупик, если t2 перехватит l2 до t1):

t1 acquires l1
t1 acquires l2 //before t2..
t1 unlocks l2
t1 unlocks l1

t2 acquires l2
t2 sleeps
t2 acquires l1
t2 unlocks l1
t2 unlocks l2

Если t2 «запускается первым» ..

t2 acquires l2
t2 sleeps
t1 acquires l1 (while t2 is still sleeping)
t1 tries to acquire l2 but blocks.. it's already acquired by t2..
t2 is finished sleeping..
t2 tries to acquire l1 but blocks.. it's already acquired by t1..

IE: вы можете отладить и увидеть его в виде:

Start T2
T2 - Locked L2
T2 - Sleeping
Start T1
T1 - Locked L1

- DeadLock .. Ни один из них не может продолжаться, если другой не разблокируется ..

Это неопределенное поведение. Вы также не должны угадывать, какой поток запускается первым.

0 голосов
/ 06 ноября 2018

Чтобы помочь объяснить «тупиковый» вопрос -

Используйте Thread.Sleep, если вы действительно хотите приостановить поток.

Но в этом случае, когда вы не хотите приостанавливать поток, просто приостановите «задачу».

Используйте что-то вроде этого:

await Task.Delay(myDuration);

Это не приостановит весь поток, а только одну задачу, которую вы хотите приостановить.

Все остальные задачи в том же потоке могут продолжать выполняться ...

...