Страницы

Поиск по вопросам

среда, 11 декабря 2019 г.

Многопоточность и блокировка в c#

#c_sharp #многопоточность


Зачем в операторе lock мы указываем какой-то объект? Не могу понять. Ясно, что этот
оператор блокирует определённый блок кода для других потоков, однако зачем указывать
какой-то объект для этого? Что это даёт? Что он вообще делает с этим объектом, который
я ему передал?
    


Ответы

Ответ 1



Допустим, есть: Object locker = new Object(); List list = new List(); И два метода в том же классе, где и locker: void lWrite() { while (True) { lock(locker) { list.Add(5); } Thread.Sleep(5000); } } void lRead() { while (True) { lock(locker) { MessageBox.Show(list.Count.ToString()); Thread.Sleep(500); } } } Важно, что в конструкции lock используется один и то тот же объект locker. Теперь пускаем методы lWrite и lRead каждый в своем потоке, один в цикле добавляет элементы в лист, другой выводит количество элементом листа на экран. Thread t1 = new Thread(lWrite); Thread t2 = new Thread(lRead ); t1.Start(); t2.Start(); Происходит при работе следующее: допустим, первый поток t1 первым доходит до lock, тогда он ставит блокировку на этом объекте и выполняет блок кода, вложенный в lock, в данном случае добавляет элемент в лист. Допустим, t1 у нас сейчас находится в состоянии добавления элемента, и в это же время поток t2 доходит до блока lock в своем куске кода, смотрит на объект блокировки и видит, что на объекте locker уже выставлена блокировка, пока она там есть, войти в вложенный в lock код поток не может и становится на ожидание. Как только t1 доделает свое добавление, т.е. код в блоке lock завершится, он снимает блокировку с объекта locker. Поток t2 видит, что блокировка снята, заходит в блок lock, ставит на объект locker свою блокировку и выполняет вложенные команды. Т.е. конструкция lock проверяет, есть ли на указанном объекте блокировка, если нет, то ставит блокировку, выполняет вложенные инструкции, затем снимает блокировку. Если есть, то останавливает поток, ожидая, когда блокировка будет снята, и затем проделывает описанное выше, ставит свою блокировку и т.д. Код, заключенный в lock, не важен, важно состояние объекта блокировки, в данном случае locker. Допустим, в данном примере в методе lRead можно делать что-то абсолютно несвязанное со списком list, пусть это и лишено в данном случае смысла. Суть в том, что делать что-то заключенное в lock будем, только когда на объекте будет снята блокировка. Если добавить в начало: Object lockerRead = new Object(); и переписать lRead: void lRead() { while (True) { lock(lockerRead) { MessageBox.Show(list.Count.ToString()); Thread.Sleep(500); } } } то при запуске циклов t1 и t2 никакой синхронизации происходить не будет, ибо каждый поток вызывает блокировку на своем собственном объекте блокировки. Они никак не связаны получаются и никакой синхронизации не будет. Еще момент: одним и тем же должен быть именно объект блокировки, а не одинаковый член класса. Допустим, имеем класс: class Test { Object locker = new Object(); public void Doing() { lock (locker) { //что то делать } } public void DoingElse() { lock (locker) { //что то делать } } } Далее создаем объекты и опять пускаем циклы: Test test1 = new Test(); Test test2 = new Test(); Thread t1 = new Thread(test1.Doing); Thread t2 = new Thread(test2.Doing); t1.Start(); t2.Start(); То никакой блокировки при работе методов Doing возникать не будет, ибо locker в данном случае у каждого класса свой. А вот если сделать так: Thread t3 = new Thread(test2.DoingElse); t3.Start(); то мы потоки t2 и t3 будут ссылаться на один и тот же объект, и методы Doing и DoingElse будут ждать друг друга. Если надо синхронизировать разные объекты, то можно, например, ввести в родительский класс статическую переменную и использовать ее в качестве объекта блокировки. Допустим, все это вызывалось в классе Program, то кидаем в него: public static Object locker = new Object(); Далее в Test правим методы: class Test { public void Doing() { lock (Program.locker) { //что то делать } } .......... } И тогда разные экземпляры: Test test1 = new Test(); Test test2 = new Test(); Будут ссылаться на один и тот же объект, и синхронизация будет происходить.

Ответ 2



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

Ответ 3



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

Комментариев нет:

Отправить комментарий