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

Блокирует() {} блокировку ресурса или блокирует кусок кода?

Я все еще смущен... Когда мы пишем что-то вроде этого:

Object o = new Object();
var resource = new Dictionary<int , SomeclassReference>();

... и имеют два блока кода, которые блокируют o при доступе к resource...

//Code one
lock(o)
{
  // read from resource    
}

//Code two
lock(o)
{
  // write to resource
}

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

(Я использую Dictionary для целей этого примера, но может быть что угодно)

Есть два случая, о которых я особенно беспокоюсь:

  • две нити, пытающиеся выполнить ту же строку кода
  • два потока, пытающихся работать на одном ресурсе

Помогает ли lock в обоих условиях?

4b9b3361

Ответ 1

Большинство других ответов обращаются к вашему примеру кода, поэтому я постараюсь ответить на ваш вопрос в названии.

Блокировка - это просто токен. Тот, у кого есть токен, может выступить так, чтобы говорить. Таким образом, объект, который вы блокируете, не имеет явного подключения к ресурсу, который вы пытаетесь синхронизировать. Пока все читатели/писатели согласны с одним и тем же токеном, это может быть что угодно.

При попытке заблокировать объект (т.е. вызывая Monitor.Enter для объекта) среда выполнения проверяет, сохраняется ли блокировка потоком. Если это так, поток, пытающийся заблокировать, приостанавливается, в противном случае он получает блокировку и переходит к выполнению.

Когда поток, удерживающий блокировку, выходит из области блокировки (т.е. вызывает Monitor.Exit), блокировка освобождается, и любые ожидающие потоки теперь могут получить блокировку.

Наконец, нужно иметь в виду пару замков:

  • Заблокируйте столько, сколько вам нужно, но больше не нужно.
  • Если вы используете Monitor.Enter/Exit вместо ключевого слова lock, поместите вызов в Exit в блок finally, чтобы блокировка была выпущена даже в случае исключения.
  • Предоставление объекта блокировки затрудняет получение информации о том, кто заблокирован и когда. Идеально синхронизированные операции должны быть инкапсулированы.

Ответ 2

Да, использование блокировки - правильный путь. Вы можете заблокировать любой объект, но, как упоминалось в других ответах, блокировка самого ресурса, вероятно, самая простая и безопасная.

Тем не менее, вы можете использовать пару блокировки чтения/записи вместо одной блокировки, чтобы уменьшить накладные расходы concurrency.

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

Теперь я больше парень из java, поэтому вам придется изменить синтаксис и выкопать некоторый документ, чтобы применить его на С#, но rw- блокировки являются частью стандартного пакета concurrency на Java, поэтому вы можете написать что-то вроде:

public class ThreadSafeResource<T> implements Resource<T> {
    private final Lock rlock;
    private final Lock wlock;
    private final Resource res;

    public ThreadSafeResource(Resource<T> res) {
        this.res = res;
        ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
        this.rlock = rwl.readLock();
        this.wlock = rwl.writeLock();
    }

    public T read() {
        rlock.lock();
        try { return res.read(); }
        finally { rlock.unlock(); }
    }

    public T write(T t) {
        wlock.lock();
        try { return res.write(t); }
        finally { wlock.unlock(); }
    }
}

Если кто-то может придумать образец кода С#...

Ответ 3

Оба блока кода заблокированы здесь. Если нить одна блокирует первый блок, а поток два пытается попасть во второй блок, ему придется подождать.

Ответ 4

Оператор lock (o) {...} скомпилирован:

Monitor.Enter(o)
try { ... }
finally { Monitor.Exit(o) }

Вызов Monitor.Enter() будет блокировать поток, если другой поток уже вызвал его. Он будет только разблокирован после того, как другой поток вызвал Monitor.Exit() на объекте.

Ответ 5

Заблокирует помощь в обоих условиях? Да.

Блокирует ли() {} блокировку ресурса или делает он блокирует кусок кода?

lock(o)
{
  // read from resource    
}

- синтаксический сахар для

Monitor.Enter(o);
try
{
  // read from resource 
}
finally
{
  Monitor.Exit(o);
}

Класс Монитор содержит коллекцию объектов, которые вы используете для синхронизации доступа к блокам кода. Для каждого объекта синхронизации Монитор сохраняет:

  • Ссылка на поток, который в настоящее время удерживает блокировку на синхронизирующем объекте; т.е. этот поток обращается к выполнению.
  • "готовая" очередь - список потоков, которые блокируются до тех пор, пока им не будет предоставлена ​​блокировка для этого синхронизирующего объекта.
  • Очередь ожидания - список потоков, которые блокируются до тех пор, пока они не будут перемещены в "готовую" очередь с помощью Monitor.Pulse() или Monitor.PulseAll().

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

Ответ 6

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

О, а объект "o" должен быть одноточечным или ограниченным везде, где требуется блокировка, поскольку то, что ДЕЙСТВИТЕЛЬНО заблокировано, является этим объектом, и если вы создаете новый, то новый не будет заблокирован все же.

Ответ 7

Способ, которым вы его реализовали, - это приемлемый способ сделать то, что вам нужно. Одним из способов улучшить ваш способ сделать это будет использование lock() для самого словаря, а не второй объект, используемый для синхронизации словаря. Таким образом, вместо того, чтобы передавать дополнительный объект, сам ресурс отслеживает, есть ли на нем собственный монитор.

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