Господа, подскажите пожалуйста зачем в операторе lock мы указываем какой-то объект ? Не могу понять. Ясно что этот оператор блокирует определенный кодовый блок для других потоков, однако зачем указывать какой-то объект для этого? Что это дает ?? B и что он вообще делает с этим объектом который я ему передал ? Спасибо
Ответ
Допустим, есть:
Object locker = new Object();
List
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();
Будут ссылаться на один и тот же объект, и синхронизация будет происходить.
Комментариев нет:
Отправить комментарий