Приложение может создать несколько потоков. Сейчас мы рассмотрим код на шаге 1 из примера Threading (Организация поточной обработки). Теперь несколько запросов о резервировании делаются одновременно.
NewReservation *reservel =
new NewReservation(customers, hotelBroker); // клиенты
NewReservation *reserve2 =
new NewReservation(customers, hotelBroker); // клиенты
// создать делегат для потоков
ThreadStart *threadStartl = new ThreadStart(
reservel,
reservel->MakeReservation);
ThreadStart *threadStart2 = new ThreadStart(
reserve2,
reserve2->MakeReservation);
Thread *threadl = new Thread(threadStartl); // новый Поток
Thread *thread2 = new Thread(threadStart2); // новый Поток
Console::WriteLine(
"Thread {0} starting a new thread.",
// "Поток {0} запустил новый поток. "
Thread::CurrentThread-> // Поток
GetHashCode().ToString());
threadl->Start(); // Пуск
thread2->Start();// Пуск
// Блокировать этот поток, пока не завершится рабочий поток
threadl->Join(); // Объединение
thread2->Join(); // Объединение
Проблема с нашей системой резервирования мест в гостинице состоит в том, что нет никакой гарантии, что один поток не будет влиять на результаты другого. Потоки выполняются только на маленьком временном интервале до того, как уступают процессор другому потоку. Поэтому они могут быть прерваны при выполнении любой операции, над которой работали, если отведенный для них временной промежуток закончился. Например, выполнение потока может быть прервано во время изменения структуры данных. Если другой поток попробует использовать информацию в этой структуре данных или модифицировать ее, то результаты этих операций будут противоречивы и неправильны, или может произойти аварийный отказ программы. (Например, в случае, когда ссылки на устаревшие структуры данных не были еще модифицированы, может произойти необрабатываемое исключение).
Рассмотрим несколько участков в коде, где происходит заказ места в гостинице и там, где происходит резервирование свободных мест — именно в этих точках могут возникнуть трудности подобного рода. Исследуем код для Broker: : Reserve (Брокер::Резерв) в Broker. h. Сначала сделаем проверку существующих заказов для данной гостиницы и для данной даты, чтобы узнать, есть ли свободные номера. Тогда, если есть в наличии свободный номер, он резервируется. Мы добавили вызов метода Thread: :Sleep (Поток-Режим ожидания) между кодом, который проверяет имеющиеся в распоряжении номера и кодом, который резервирует свободный номер. Вскоре будет объяснено, для чего это было сделано.
// Проверить, есть ли номера для всех дат
for (int i = day; i < day + numDays; i++)
{
if (numCust[i, unitid] >= unit->capacity)
{
result->Reservation!d = -1; // результат
result->Comment = "Room not available";
// результат-> Комментарий = "Номера не доступны";
return result; // результат
}
}
Console::WriteLine(
"Thread {0} finds the room is available in
Broker::Reserve",
// "Поток {0} находит, что номер доступен в
// Брокер:: Резерв ",
Thread::CurrentThread-> // Поток GetHashCode{).ToString());
Thread::Sleep(0); // Поток:: Бездействие
// Резервировать номер для требуемых дат
for (int i = day; i < day + numDays; i++)
numCust[i, unitid] += 1;
Этот код может привести к противоречивым результатам! Один из потоков может быть прерван сразу после того, как обнаружит последний имеющийся в распоряжении номер, но прежде, чем он получит шанс сделать заказ. Другой поток, которому предоставляется отрезок процессорного времени, может найти тот же самый доступный номер гостиницы и сделать заказ. Когда первый поток запускается снова, он начинает работу с точки, где был прерван, и также закажет тот же самый последний номер в гостинице.
Чтобы смоделировать возникновение такой ситуации, на этом шаге (шаг 1) примера Threading (Организация поточной обработки) поместим вызов метода Thread:: Sleep (Поток::Режим ожидания) между кодом, который проверяет имеющиеся в распоряжении номера и кодом, который резервирует свободный номер. Вызов Sleep (0) заставляет поток прекратить выполнение и уступить остаток своего временного отрезка.
Чтобы удостовериться в том, что мы наблюдаем именно возникновение проблемы соперничества между потоками, мы имеем в своем распоряжении всего один номер в целой гостинице! Затем мы подготавливаем нашу программу так, чтобы два потока пробовали резервировать единственный номер в гостинице на ту же самую дату. Рассмотрим код в подпрограмме Main (Главная), который все это подготавливает:
hotelBroker->AddHotel(
"Boston", // "Бостон",
"Presidential", // "Президентская",
1, // только один номер во всей гостинице!
(Decimal) 10000); // (Десятичное число)
NewReservation *reservel =
new NewReservation(customers, hotelBroker); // клиенты
reservel->customerld = 1;
reservel->city = "Boston"; // город = "Бостон";
reservel->hotel = "Presidential"; // гостиница = "Президентская";
reservel->sdate = "12/12/2001";
reservel->numberDays = 3;
NewReservation *reserve2 =
new NewReservation(customers, hotelBroker); // клиенты
reserve2->customerld = 2;
reserve2->city = "Boston"; // город = "Бостон";
reserve2->hotel = "Presidential"; // гостиница = "Президентская";
reserve2->sdate = "12/13/2001";
reserve2->numberDays = 1;
Added Boston Presidential Hotel with one room.
Thread 3 starting a new thread.
Thread 5 starting.
Thread 6 starting.
Reserving for Customer 2 at the Boston Presidential Hotel on
12/13/2001 12:00:00 AM for 1 days
Reserving for Customer 1 at the Boston Presidential Hotel on
12/12/2001 12:00:00 AM for 3 days
Thread 6 entered Broker::Reserve
Thread 6 finds the room is available in Broker::Reserve
Thread 5 entered Broker::Reserve
Thread 5 finds the room is available in Broker::Reserve
Thread 6 left Broker::Reserve
Reservation for Customer 2 has been booked
Reservationld = 1
Thread 5 left Broker::Reserve
Reservation for Customer 1 has been booked
Reservationld = 2
ReservationRate = 10000
ReservationCost = 30000
Comment = OK
ReservationRate = 10000
ReservationCost = 10000
Comment = OK
Done!
Перевод такой:
Добавлена Бостонская Президентская гостиница с одним номером.
Поток 3 стартовал новый поток.
Поток 5 стартовал.
Поток 6 стартовал.
Резервирование для Клиента 2 в Бостонской Президентской гостинице на
12/13/2001 12:00:00 AM в течение 1 дня
Резервирование для Клиента 1 в Бостонской Президентской гостинице на
12/12/2001 12:00:00 AM в течение 3 дней
Поток 6 ввел Брокер:: Резерв
Поток 6 находит, что номер доступен в Брокер::Резерв
Поток 5 ввел Брокер::Резерв
Поток 5 находит, что номер доступен в Брокер::Резерв
Поток 6 выходит из Брокер::Резерв
Резервирование для Клиента 2 было заказано
Reservationld = 1
Поток 5 выходит из Брокер::Резерв
Резервирование для Клиента 1 было заказано
Reservationld = 2
ReservationRate = 10000
ReservationCost = 30000
Комментарий = OK
ReservationRate = 10000
ReservationCost = 10000
Комментарий = OK
Сделано!
К сожалению, оба клиента добиваются резервирования последнего (единственного) номера на 13 декабря! Обратите внимание на то, как один из потоков выполняет метод Reserve (Резерв) и находит, что номер доступен прежде, чем прекратит свою работу. Затем другой поток выполняет Reserve (Резерв) и также обнаруживает свободный номер прежде, чем закончится отведенный для него промежуток времени. Потом оба потока заказывают тот же самый номер гостиницы.
Операционные системы предоставляют средства синхронизации взаимодействия нескольких потоков и процессов, получающих доступ к совместно используемым ресурсам. Каркас .NET Framework предусматривает несколько механизмов предотвращения конфликтов между потоками.
Каждый объект в каркасе .NET Framework может использоваться в качестве синхронизирующей секции кода (критической секции). В пределах такой секции одновременно может выполняться только один поток. Если один поток уже выполняется внутри такой синхронизирующей секции кода, любые другие потоки, пытающиеся получить доступ к данной секции, блокируются (и ждут) до тех пор, пока выполняющийся поток не покинет эту секцию кода.