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

Пример кода, иллюстрирующий тупик с помощью блокировки (это)

Я прочитал несколько статей и сообщений, в которых говорится, что lock(this), lock(typeof(MyType)), lock("a string") - это плохая практика, потому что другой поток может заблокировать один и тот же ключ и вызвать тупик. Чтобы понять эту проблему, я пытался создать образец кода, чтобы проиллюстрировать тупик, но не смог обернуть это вокруг.

Может ли кто-нибудь написать сжатый фрагмент кода, который иллюстрирует эту классическую проблему? Пожалуйста, держите его коротким, я могу переваривать код только в небольших кусках.

Изменить Я думаю, что Лассевк подводит итог; что реальная проблема в том, что вы потеряли контроль над своими замками. Как только это произойдет, вы не сможете контролировать порядок вызывания блокировок, и вы разрешаете потенциальную ситуацию взаимоблокировки.

lock(this), lock(typeof(MyType)) и т.д., все это ситуации, когда вы выбрали блокировку, которую невозможно контролировать.

4b9b3361

Ответ 1

Тупик будет возникать только в том случае, если у вас есть несколько блокировок. Вам нужна ситуация, когда в обоих потоках содержится ресурс, который нужен другому (что означает, что должно быть не менее двух ресурсов, и два потока должны пытаться их получить в другом порядке)

Итак, простой пример:

// thread 1
lock(typeof(int)) {
  Thread.Sleep(1000);
  lock(typeof(float)) {
    Console.WriteLine("Thread 1 got both locks");
  }

}

// thread 2
lock(typeof(float)) {
  Thread.Sleep(1000);
  lock(typeof(int)) {
    Console.WriteLine("Thread 2 got both locks");
  }
}

Предполагая, что оба потока запущены в течение секунды друг от друга, они оба успеют захватить первый замок, прежде чем кто-либо попадет во внутренний замок. Без вызова Sleep() один из потоков, скорее всего, успеет получить и освободить обе блокировки до того, как будет запущен другой поток.

Ответ 2

Конечно, здесь вы идете.

Обратите внимание, что общий пример тупика - это когда вы приобретаете несколько блокировок, а два или более потока в очереди ожидают друг друга.

Например, два потока, которые блокируются следующим образом:

Thread 1               Thread 2
 Lock "A"               Lock "B"
 Lock "B"               Lock "A" <-- both threads will stop dead here
                                     waiting for the lock to be come
                                     available.

Однако в этом примере я этого не беспокоился, я просто позволяю одному потоку блокировать бесконечно. Вы действительно не хотите потерять контроль над своими замками, поэтому, хотя это надуманный пример, тот факт, что фоновый поток полностью блокирует основной поток, как это, плохо.

using System;
using System.Threading;

namespace ConsoleApplication7
{
    public class Program
    {
        public static void Main(string[] args)
        {
            LockableClass lockable = new LockableClass();
            new Thread(new ParameterizedThreadStart(BackgroundMethod)).Start(lockable);
            Thread.Sleep(500);
            Console.Out.WriteLine("calling Reset");
            lockable.Reset();
        }

        private static void BackgroundMethod(Object lockable)
        {
            lock (lockable)
            {
                Console.Out.WriteLine("background thread got lock now");
                Thread.Sleep(Timeout.Infinite);
            }
        }
    }

    public class LockableClass
    {
        public Int32 Value1 { get; set; }
        public Int32 Value2 { get; set; }

        public void Reset()
        {
            Console.Out.WriteLine("attempting to lock on object");
            lock (this)
            {
                Console.Out.WriteLine("main thread got lock now");
                Value1 = 0;
                Value2 = 0;
            }
        }
    }

}

Ответ 3

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

Объекты типа - это синглтоны, видимые для каждой части кода .net, и вы не можете контролировать, кто блокирует ваш объект "this" извне.

То же самое для строк: поскольку строки неизменяемы, структура хранит только один экземпляр "жестко закодированных" строк и помещает их в пул (строка, как говорят, интернирована), если вы пишете два раза в своем коде строка "hello", вы всегда получите ту же самую ошибку.

Рассмотрим следующий пример: вы написали только Thread1 в своем супер приватном вызове, а Thread2 вызывается некоторой библиотекой, которую вы используете в фоновом потоке...

void Thread1()
{
  lock (typeof(int))
  {
    Thread.Sleep(1000);
    lock (typeof(long))
      // do something
  }
}

void Thread2()
{
  lock (typeof(long))
  {
    Thread.Sleep(1000);
    lock (typeof(int))
      // do something
  }
}

Ответ 4

Это довольно стандартная ошибка. Захват замков не по порядку, а затем спящий с замком. Два плохих дела.:)

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace DeadLock
{
    public class Program
    {
        static void Main(string[] args)
        {
            var ddt = new DontDoThat();

            ddt.Go();
        }
    }

    public class DontDoThat
    {
        private int _badSharedState = 0;
        private readonly object _lock1 = new object();
        private readonly object _lock2 = new object();

        public void Go()
        {
            new Thread(BadGuy1).Start();
            new Thread(BadGuy2).Start();

            Console.WriteLine("Leaving Go!");
        }

        public void BadGuy1()
        {
            lock (_lock1)
            {
                Thread.Sleep(100); // yeild with the lock is bad
                lock (_lock2)
                {
                    _badSharedState++;
                    Console.Write("From Bad Guy #1: {0})", _badSharedState );
                }
            }
        }
        public void BadGuy2()
        {
            lock (_lock2)
            {
                lock (_lock1)
                {
                    _badSharedState++;
                    Console.Write("From Bad Guy #2: {0})", _badSharedState);
                }
            }
        }
    }
}

Ответ 5

class Character
{
    public Character Other;
    public string Name;
    private object locker = new object();

    public Character(string name)
    {
        Name = name;
    }

    public void Go()
    {
        lock (locker)
        {
            Thread.Sleep(1000);
            Console.WriteLine("go in {0}", Name);
            Other.Go();
        }
    }
}

class Program
{
    static void Main(string[] args)
    {
        Character a = new Character("A");
        Character b = new Character("B");
        a.Other = b;
        b.Other = a;

        new Thread(a.Go).Start();
        b.Go();

        Console.ReadLine();
    }
}

Ответ 6

Проблема заключается в том, что блокировка ( "строка" ) блокируется одним синглтоном. Это означает, что другие объекты, которые используют один и тот же замок, могут быть бесконечными.

например:

using System;
using System.Threading;

namespace ThreadLock
{
    class Program
    {
        static void Main(string[] args)
        {
            lock ("my lock")
            {
                ManualResetEvent evt = new ManualResetEvent(false);
                WorkerObject worker = new WorkerObject(evt);
                Thread t = new Thread(new ThreadStart(worker.Work));
                t.Start();
                evt.WaitOne();
            }
        }
    }

    class WorkerObject
    {
        private ManualResetEvent _evt;
        public WorkerObject(ManualResetEvent evt)
        {
            _evt = evt;
        }
        public void Work()
        {
            lock ("my lock")
            {
                Console.WriteLine("worked.");
                _evt.Set();
            }
        }
    }
}

В этом случае вызывающий код создает блокировку строки, а затем создает рабочий объект. Рабочий объект в Work() блокирует одну и ту же строку, которая является одноэлементной на С#. Он оказывается в тупике, потому что вызывающий владеет блокировкой и ждет сигнала, который никогда не наступит.