Пример кода для иллюстрации тупика с помощью блокировки (это) - PullRequest
29 голосов
/ 21 мая 2009

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

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

Edit: Я думаю, что lassevk суммирует это хорошо; что настоящая проблема в том, что вы потеряли контроль над своими замками. Как только это происходит, вы не можете контролировать порядок вызова блокировок, и вы допускаете потенциальную тупиковую ситуацию.

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

Ответы [ 6 ]

32 голосов
/ 21 мая 2009

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

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

// 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 () один из потоков, скорее всего, успеет получить и снять обе блокировки до того, как другой поток даже запустится.

4 голосов
/ 21 мая 2009

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

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

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

Рассмотрим следующий пример: вы написали только 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
  }
}
3 голосов
/ 21 мая 2009

Конечно, вот, пожалуйста.

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

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

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;
            }
        }
    }

}
2 голосов
/ 21 мая 2009

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

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);
                }
            }
        }
    }
}
0 голосов
/ 06 апреля 2011
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();
    }
}
0 голосов
/ 21 мая 2009

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

например:

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 () блокируется на той же строке, которая является одиночной в C #. Он оказывается в тупике, потому что вызывающий абонент владеет блокировкой и ожидает сигнала, который никогда не придет.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...