{ for (inti = 1; i<= 10; i++)
{ myWait.WaitOne(); //ожиданиеобъекта-события
for (int j = 0; j < 100000; j++) sum++;
myWait.Reset(); //освобождениеобъекта-события
Console.WriteLine("Поток {0} sum = {1,-7} время = {2,-3} итерация = {3} ",
Thread.CurrentThread.ManagedThreadId,
sum,
Environment.TickCount - myTime,i);
Thread.Sleep(5);
}
}
Public void Test_()
{sum = 0; //общийразделяемыйресурс
myTime = Environment.TickCount;
Thread[] tt = { new Thread(MyThread_), new Thread(MyThread_), new Thread(MyThread_) };
for (inti = 0; i< 3; i++) tt[i].Start(); // запускпотоков
while (true)
{ myWait.Set(); //объект переведен в состояние “сигнализрует”
boolttt = tt[0].IsAlive || tt[1].IsAlive || tt[2].IsAlive;
i f (!ttt) break; // выход, если все потоки “не живы ”
|
|
Thread.Sleep(5);
}
// for (inti = 0; i< 3; i++) tt[i].Join();
Console.WriteLine("sum = {0}, времявыполнения = {1} ", sum, Environment.TickCount-myTime);
}
Результат работы
Поток 3 sum = 100000 время = 0 итерация = 1
Поток 4 sum = 200000 время = 16 итерация = 1
Поток 5 sum = 300000 время = 31 итерация = 1
Поток 3 sum = 400000 время = 47 итерация = 2
Поток 4 sum = 500000 время = 63 итерация = 2
...
Поток 4 sum = 2600000 время = 391 итерация = 9
Поток 5 sum = 2700000 время = 406 итерация = 9
Поток 3 sum = 2800000 время = 422 итерация = 10
Поток 4 sum = 2900000 время = 438 итерация = 10
sum = 2900878, время выполнения = 453
Поток 5 sum = 3000000 время = 453 итерация = 10
Для продолжения нажмите любую клавишу...
Результат получен правильный, но следует иметь в виду, что оператор Console.Writeline(….) исполняет потоковую операцию вывода, т.е. порождает новый поток, который исполняется параллельно с существующими. Так как для консольного оператора в главном потоке имеются все выводимые значения (пусть даже не в окончательном варианте), то оператор готов к выполнению и может быть выполнен раньше вывода в дочернем потоке, который формирует и выводит окончательные значения результатов. Поэтому целесообразно оставить операторы ожидания завершения потоков, т.е. for (inti = 0; i< 3; i++) tt[i].Join();
Результат работы в этом случае (конец вывода)
...
Поток 4 sum = 2700000 время = 407 итерация = 9
Поток 3 sum = 2800000 время = 422 итерация = 10
Поток 5 sum = 2900000 время = 438 итерация = 10
Поток 4 sum = 3000000 время = 453 итерация = 10
sum = 3000000, время выполнения = 469
Для продолжения нажмите любую клавишу...
Следует иметь в виду, что слишком большое количество блокировок тоже может приводить к проблемам, например, к взаимоблокировке. Взаимоблокировкой (deadlock) называется ситуация, когда как минимум два потока останавливаются и ожидают друг от друга снятия блокировки. Поскольку оба потока ожидают друг от друга выполнения соответствующего действия, получается, что они блокируют друг друга, из-за чего их ожидание может длиться бесконечно.
|
|
Задача “Поставщики - потребители”
Суть задачи сводится к следующему: имеется общий разделяемый ресурс, в который поставщики записывают информацию, а потребители читают. Объем разделяемого ресурс может допускать запись нескольких единиц информации (нескольких сообщений от поставщиков) или только одной. В рассматриваемом примере ресурс будет содержать одно сообщение.
В качестве общего разделяемого ресурса удобно использовать объект класса-шаблона Queue<T>, который предоставляет удобные свойства и методы:
Count – текущее число элементов в очереди,
Enqueue() – добавление сообщения в конец очереди,
Dequeue() – извлечение с удалением начального элемента очереди.
Частный случай: один или несколько поставщиков, 1 потребитель. Данный случай определяет ограничения на поведение участников процесса:
доступ к ресурсу должен быть монопольным, т.е. недопустимы одновременная запись сообщений от разных поставщиков или одновременная запись и чтение сообщения;
чтение сообщения возможно только при наличии сообщений в очереди (ресурс не пуст).
Class SupNCons1
{ objectlock_obj = new object(); // объектблокировки
Queue<string>Qchange = new Queue<string>();
int N1_sup = 5; // число сообщений для каждого поставщика
intNmsg = 5; //общее число сообщений для потребителя
voidSup(objectnum) // метод для потока поставщика
{ string n = Convert.ToString(num); // аргумент -№ поставщика
Randomr = newRandom(); //объект используется для формирования случайного
//значениязадержки
for (inti = 0; i< N1_sup; i++)
{ lock (lock_obj) { Qchange.Enqueue("сообщение №" + (i + 1).ToString() + " от " + n);
// блокировка и запись сообщения в очередь
}
intdel = Convert.ToInt32(r.NextDouble()) * 3 + 1; //преобразование вещ. в целое от1 до 3
Thread.Sleep(del); //Задержка потока
}
}
Void Cons()
{ stringstr = "";
intnread = 0;
boolt;
while (nread<Nmsg) //цикл чтения сообщений
{ t = false;
lock (lock_obj) // блокировка общего ресурса
{ if (Qchange.Count> 0) { str = Qchange.Dequeue(); t = true; }
// чтение и удаление первого сообщения из очереди
} // еслиресурснепуст
if (t == false) { Thread.Sleep(50); // задержка, еслиресурспуст
}
else { Console.WriteLine(str);
nread++;// изменение счетчика прочитанных сообщений
}
}
Public void Test()
{ Thread ts1 = new Thread(Sup);
Thread ts2 = new Thread(Sup);
N1_sup = 10;
Nmsg = 2 * 10;
Thread tc = new Thread(Cons);
ts2.Start(2);
tc.Start();
ts1.Start(1);
}
} // конец класса SupNCons1
|
|
Результат выполнения
сообщение №1 от 2
сообщение №1 от 1
сообщение №2 от 1
сообщение №2 от 2
сообщение №3 от 2
сообщение №3 от 1
сообщение №4 от 2
сообщение №4 от 1
сообщение №5 от 1
сообщение №5 от 2
сообщение №6 от 2
сообщение №6 от 1
сообщение №7 от 1
сообщение №7 от 2
сообщение №8 от 2
сообщение №9 от 2
сообщение №8 от 1
сообщение №9 от 1
сообщение №10 от 2
сообщение №10 от 1
Для продолжения нажмите любую клавишу...
Вариант 2: N поставщиков, M потребителей. Требуется, чтобы каждый потребитель прочитал все сообщения от всех поставщиков. На поведение участников процесса накладываются ограничения:
· Доступ к ресурсу должен быть монопольным.
· Сообщение может быть удалено из общего ресурса, при условии, что оно прочитано всеми потребителями.
Следует отметить также, что поток потребителя может многократно обращаться к ресурсу и должен уметь распознать, является ли текущее сообщение новым для него или уже прочитанным. Таким образом, число обращений потребителя к ресурсу обычно больше, чем NS * Nmsg, где* NS - число поставщиков, Nmsg - число поставок от каждого поставщика.
Общий разделяемый ресурс в многопоточных приложениях часто оформляют в виде структур или классов. Класс Queue<string> в данном случае не обеспечивает выполнения 2-го ограничения, т.к. метод Dequeue() сразу удаляет элемент из очереди. Данная проблема решается с помощью наследования:
classMyQueue:Queue<string>
{ staticstringlast_res = ""; //текст очередного сообщения, которое должны
// прочитать все потребители
publicintNc = 0; //число всех потребителей
staticintNc_read = 0; //число потребителей, прочитавших текущее сообщение