Lock () {} блокирует ресурс или блокирует фрагмент кода? - PullRequest
15 голосов
/ 15 апреля 2009

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

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 для целей этого примера, но может быть что угодно)

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

  1. два потока пытаются выполнить одну и ту же строку кода
  2. два потока пытаются работать на одном ресурсе

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

Ответы [ 7 ]

12 голосов
/ 15 апреля 2009

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

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

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

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

Наконец, несколько вещей, которые следует иметь в виду в отношении замков:

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

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

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

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

Теперь я в большей степени Java-парень, так что вам придется изменить синтаксис и найти какой-то документ, чтобы применить его в C #, но rw-lock являются частью стандартного пакета параллелизма в 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(); }
    }
}

Если кто-то может придумать пример кода C # ...

2 голосов
/ 15 апреля 2009

Поможет ли блокировка в обоих случаях? Да.

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

lock(o)
{
  // read from resource    
}

является синтаксическим сахаром для

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

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

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

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

2 голосов
/ 15 апреля 2009

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

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

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

2 голосов
/ 15 апреля 2009

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

0 голосов
/ 15 апреля 2009

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

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

0 голосов
/ 15 апреля 2009

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

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

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