Блокировка области видимости в C #: возвращаемый объект все еще «заблокирован»? - PullRequest
5 голосов
/ 01 апреля 2012

Предполагается, что у меня есть объект A, содержащий

// ...

private List<double> someList = new List<double>();

// ... 

public List<double> SomeList
{
    get { lock (this) { return someList; } }
}

// ...

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

A.SomeList.Add(2.0);

или

A.SomeList.RemoveAt(0);

Другими словами, когда снимается блокировка?

Ответы [ 6 ]

12 голосов
/ 01 апреля 2012

Здесь нет безопасности потока.

lock освобождается, как только завершается блок, который он защищает, непосредственно перед возвратом свойства, поэтому вызовы Add ad RemoveAt не защищены блокировкой.

5 голосов
/ 01 апреля 2012

Блокировка, показанная в этом вопросе, не очень полезна.

Чтобы сделать операции со списком потокобезопасными, вам необходимо реализовать свои собственные методы Add / Remove / etc, заключающие их в список.

public void Add(double item)
{
    lock(_list)
    {
        _list.Add(item);
    }
}

Также неплохо бы скрыть сам список от потребителей вашего класса, т.е. сделать поле приватным.

3 голосов
/ 01 апреля 2012

Блокировка снимается при выходе из тела оператора lock. Это означает, что ваш код не поточно-ориентированный.

Другими словами, вы можете быть уверены, что два потока не будут выполнять return someList для одного и того же объекта одновременно. Но, безусловно, возможно, что один поток выполнит Add() одновременно с тем, как другой поток выполнит RemoveAt(), что делает его не поточно-безопасным.

2 голосов
/ 01 апреля 2012

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

1 голос
/ 01 апреля 2012

Ладно, черт побери.
Существует способ сделать ваш объект потокобезопасным, используя архитектуры, которые уже потокобезопасны.

Например, вы можете сделать ваш объект однопоточным COM объектом. COM-объект будет поточно-ориентированным, но вы будете платить за производительность (цена за ленивость и отсутствие управления собственными блокировками).

Создание COM-объекта в C #

0 голосов
/ 01 апреля 2012

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

  • Во-первых, lock (this) {...} предлагает «охват» - например, как using (){} - он только блокирует (это в данном случае) переменные внутри. И это «хорошо» :) на самом деле, как если бы вы не могли полагаться на то, что вся концепция блокировок / синхронизации была бы очень бесполезна,
  • lock (this) { return something; } - это своего рода оксюморон - он возвращает то, что разблокирует в тот же момент, когда он возвращается,
  • И проблема, я думаю, заключается в понимании того, как это работает. «lock ()» не «сохраняется» в состоянии объекта, так что вы можете вернуть его и т. д. Посмотрите здесь, как реализовано Как именно работает блокировка? - ответ объясняет Это. Это скорее «критическая секция» - то есть вы защищаете определенные части «кода», который использует переменную, а не саму переменную. Для любого типа синхронизации требуются «объекты синхронизации» для удержания блокировок, и они должны быть удалены, когда блокировка больше не нужна. Взгляните на этот пост https://stackoverflow.com/a/251668/417747, Эстебан сформулировал это очень хорошо,

"Наконец, существует распространенное заблуждение, что lock (this) фактически изменяет объект, переданный в качестве параметра, и некоторым образом делает его доступным только для чтения или недоступным. Это ложно. Объект, переданный в качестве параметра для блокировки просто служит ключом " (это цитата)

  • вы либо (обычно) блокируете «закрытый код» метода класса, свойство ... - чтобы синхронизировать доступ к тому, что вы делаете внутри - и доступ к приватному члену (опять же обычно), чтобы никто другой не мог доступ к нему без прохождения вашего синхронизированного кода.
  • Или вы создаете поточно-ориентированную «структуру» - например, список - которая уже «синхронизирована» внутри, чтобы вы могли обращаться к ней поточно-ориентированным способом. Но не существует таких вещей (или не используемых, почти никогда), как передача блокировок вокруг одного или блокировка переменной в одном месте, в то время как другая часть кода разблокирует ее и т. Д. (В этом случае это некоторый тип EventWaitHandle, который скорее используется для синхронизации вещей между «далеким» кодом, когда один срабатывает на другом и т. д.)
  • В вашем случае, я думаю, стоит выбрать «синхронизированную структуру», то есть список, который обрабатывается внутри,
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...