Допустим имеется метод обращающийся к полям класса, метод вызывается в потоках. Как я понял, поля класса являются разделяемыми между всеми потоками с этим методом, и нужно обращаться к ним с помощью примитивов синхронизации(lock’ать). А объекты создаваемые в методе не являются общими между потоками и не могут пересечься, следовательно к ним общий доступ реализовывать не нужно. Но не совсем понятно, что происходит когда к объекту создаваемому присваивается значение поля, поэтому приходится и объект локать. Собственно вопрос: что именно нужно локать в методе, а что нет. И для чего разделяемые поля часто помечают модификатором static? И не понятно почему поток может застрять внутри лока функции pop, если здесь Monitor.Enter и Exit, в конструкции Wait не используется.
bool TryPop(out int num){
lock(_locker){
return _stack.TryPop(out num);
}
}
void Push(int num){
lock(_locker){
_stack.Push(num);
}
}
Спасибо за прочтение, подскажите куда копать.
Если говорить очень упрощенно (то есть это не всегда верно, но для понимания сойдет), то в C# есть 2 области памяти: стек и куча.
Куча служит для хранения объектов. Например, экземпляров классов. Эта куча доступна для любого потока вашей программы. Вопрос только в том, знает ли поток что из жтой кучи взять. Напирмер, если у вас в куче хранится объект и поток А имеет ссылку на него, то есть он знает, то по такуому то адресу в куче лежит экземпляр такого то класса - то он может этот экземпляр класса считать и вызвать его методы/свойства/поля и тд. Но если поток Б не имеет ссылки на экземпляр и ему неоткуда её взять, то он так и не узнает, что там в куче. Простой пример.
void MyFunction()
{
var myObject = new MyClass();
}
В коде выше myObject
- это переменная, которая хранит адрес объекта, то есть адрес экземпляра класса MyClass
. Этот объект живет, пока выполняется функция. Адрес этого объекта никуда не передается. Таким образом, об этом объекте знает только тот поток, который в данный момент выполняет эту функцию, только он имеет к нему доступ. Этот код выше обычно получается потокобезопасным (если сам MyClass
внутри себя не содержит ничего, что ломает его потокобезопасность, например обращение к статическим полям).
Теперь давайте усложним задачу и добавим поле
private MyClass myField = new MyClass();
void MyFunction()
{
var myObject = myField;
}
И мы видим, что объект сам, то есть экземпляр MyClass
также будет лежать в куче, но его область видимости не ограничена функцией. Любой из потоков может получить доступ к объекту через поле класса. Раз любой поток может это сделать, то значит любой поток может работать с этим объектом, вызывать методы, иенять состояние и тд. Код выше не является потокобезопасным.
Таким образом, когда мы говорим о разделяемом доступе, можно сказать, что не важно как был создан объект, н если он лежит в куче и у потока есть ссылка на него, то поток может его считать и с ним работать.
Чтобы говорить о многопоточности и стеке, надо понимать, что такое стек. Стек - это, грубо говоря, временное хранилище состояний функций. Например, поглядит следующий код:
void MyFunction1()
{
int i1 = 10;
MyFunction2();
i1 = 15;
}
void MyFunction2()
{
int i2 = 10;
}
Допустим, мы вызываем функцию MyFunction1()
, она начинает выполняться, и потом доходит до вызова MyFunction2();
- что при этом происходит? Как происходит вызов одной функции из другой?
Вот примерный псевлоалгоритм:
MyFunction1()
int i1
i1
значение 10
MyFunction2();
int i2
i2
значение 10i2
MyFunction2();
i1
значение 15i1
MyFunction1();
Отсюда можно сделать несколько выводов:
И вот тут в игру вступают отличия значимых и ссылочных типов. Поглядим на этот пример
private MyClass myField = new MyClass();
void MyFunction()
{
var myObject = myField;
}
Здесь переменная myObject
представляет собой адрес в памяти в куче. Это не сам объект - это инструкция, где его искать. Значение это переменной (то есть адрес объекта) будет храниться в стеке, но сам объект - в куче. Таким образом, другие потоки не смогут изменить значение переменной myObject
, но они могут изменить объект, который ледит по адресу, куда указывает myObject
.
А вот в этом примере
private int myField = 10;
void MyFunction()
{
var myInt = myField;
}
Вот тут var myInt = myField;
происходит копирование значения myField
в переменую myInt
и значение переменной myInt
будет лежать в стеке. То есть после присваивания уже никто, кроме текущего потока, не сможет изменить значение myInt
, так как оно полностью лежит в стеке и не имеет ничего общего с кучей.
И для чего разделяемые поля часто помечают модификатором static?
Это утверждение не соотвенствует действительности.
почему поток может застрять внутри лока функции pop, если здесь Monitor.Enter и Exit, в конструкции Wait не используется
lock
- это и есть Monitor.Enter
+ Monitor.Exit
+ try-finally
.
что именно нужно локать в методе, а что нет
Если говорить об общем случае, то использовать блокировку нужно тогда, когда нужно обеспечить целостность разделяемого ресурса (т.е. обеспечить сохранение инвариантов, например гарантировать, что операция increment
всегда увеличивает счетчик ровно на 1) при операциях, которые не являются атомарными без явной блокировки.
Второе использование это безопасная публикация изменений, т.е. чтобы гарантировать, что другие потоки увидят изменения в состоянии сделанные потоком.
Под разделяемым ресурсом тут понимается объект, к которому есть доступ из разных потоков.
Отсюда следует, что если к объекту возможен доступ только из одного потока, то нет смысла использовать блокировки при работе с ним. Это вы скорее всего имеете ввиду (но используете не совсем точные формулировки) тут:
объекты создаваемые в методе не являются общими между потоками и не могут пересечься
Правильно будет сказать "если объект создается потоком, но потом ссыкла на объект не присваивается в статические поля или в поля объектов доступных другим потокам а также созданный объект не использует внутри себя другие объекты доступные из других потоков".
Также из этого следует, что неизменямый (immutable) объект можно безопасно использовать из разных потоков без блокировок (т.к. в нем нет модифицирующих операций).
для чего разделяемые поля часто помечают модификатором static
Скорее всего вы тут путаете причину и следствие. Статические поля доступны всем потокам и поэтому они являются разделяемыми.
Айфон мало держит заряд, разбираемся с проблемой вместе с AppLab
Перевод документов на английский язык: Важность и ключевые аспекты
Какие существуют виды рекламных бордов и как выбрать подходящий?
А также:
У меня есть код который должен работать 24/7 и есть сервер через который я должен его запускатьТо есть отправил запрос http://site
Делаю 2д платформер, захотел двериПо идее должно работать так - есть дверь, и у нее есть linkeddoor, при нажатии E пока в двери происходит телепортация...