Системы массового обслуживания (СМО) | |
Классификация СМО | Практический пример моделирования СМО |
Показатели эффективности СМО | |
Экономические показатели СМО | |
Pотк = Pm, где m - число каналов обслуживания.
Pотк = Pm + L, где L — допустимая длина очереди.
Pобсл = 1 - Pотк
m+L Mож = ∑[(n-m)*Pn] n=m+1где Pn — вероятность того, что в системе находятся n требований.
L Mож = (P0*ρm / m!) * ∑(n*(ρ/m)n) n=1, где
Mож = ((P0*ρm+1) / (m * m!)) * (1 / (1 - ρ/m)2)
q = 1 - Pотк A = λ*q
m3 = ρ*qДля системы массового обслуживания с отказами m3 можно найти по формуле:
m m3 = ∑(n * Pn) n=1
M = m3
M = m3 + Mож
∞ Tож = M[Tож] = ∫(t dF) 0Тож при показательном законе распределения требований во входящем потоке можно определить по формуле:
Тож = Mож / λ
E = Pобсл*λ*c*T - Gп, где
Gп = (qk * m3 + qy * Pотк * λ + qпк * mсв)T, где:
Gп = (qож*Mож + qпк * mсв + qk * m3)T, где qож - стоимость потерь, связанных с простоем требований в очереди в единицу времени.
Практический пример моделирования СМО
Давайте попробуем теперь смоделировать СМО (на примере работы банка)...Type T = PTClient; PTQueueItem = ^TQueueItem; TQueueItem = object item: T; next: PTQueueItem; constructor create(value: T); end; { Реализуются 2 основные операции - помещение в очередь (put), и извлечение из нее (get) } TQueue = object constructor create; destructor destroy; procedure get(var value: T); procedure put(const value: T); function is_empty: boolean; private front, back: PTQueueItem; end;Реализация очереди не представляет собой ничего сверхъестественного, тем более вот здесь Очередь (FIFO: First In First Out) она уже рассматривалась, поэтому останавливаться на ней я не буду...
type PTClient = ^TClient; TClient = object constructor create(value: integer); function get_id: integer; private ID: integer; end;На Кассире следует остановиться более подробно, т.к. функции, выполняемые объектом его типа более сложные, чем функции Клиента:
type status = ( { состояние "занят: работаю с клиентом" } _busy = 0, { состояние: "свободен - жду очередного клиента" } _waiting, { состояние: "отдыхаю" } _vacation ); PTCasher = ^TCasher; TCasher = object state: status; work_time, ID: integer; { *** const *** } working_time, this_client: integer; public constructor create(_id, _work: integer); { Функция Handle вызывается ежеминутно (по времени модели) из Менеджера, и возвращает текущее состояние кассира (как результат) и через параметр changed - признак того, что состояние изменилось } function handle(var changed: integer): status; { Эта функция просто напросто имитирует подход клиента к кассиру: переводит кассира в состояние "занят" и устанавливает время обслуживания данного клиента... } procedure next; end;Еще один необходимый для нашей модели тип - Менеджер:
type TManager = object { Включает в себя очередь "посетителей" (Клиентов), массив "Кассиров" ... } queue: TQueue; cash_workers: array[0 .. pred(n)] of PTCasher; { ... текущее время (0 - при начале работы банка), и время после прихода последнего посетителя } current_time, after_prev_enter: integer; public constructor create; destructor destroy; procedure run; end;Единственное, что осталось - привести код самой программы, эмулирующей работу банка... Код достаточно хорошо прокомментирован, поэтому разобраться в логике не должно составить труда... Ниже можно скачать исходный код в виде pas-файла без комментариев...
{ Константы и функции, задающие начальные условия } const n = 10; n5 = 6; n6 = 3; nT = 50; { Для простоты реализации и понимания программы функции возвращают равномерно распределенные случайные числа, не зависящие от параметров; для идеального выполнения всех условий указанных в задаче, нужно заменить их на функции, генерирующие СВ по заданному закону распределения ... } function f1: integer; begin f1 := random(4) + 2; end; function f2: integer; begin f2 := random(10); end; (***** TClient *****) type PTClient = ^TClient; TClient = object constructor create(value: integer); function get_id: integer; private ID: integer; end; constructor TClient.create(value: integer); begin ID := value; end; function TClient.get_id: integer; begin get_id := ID; end; type T = PTClient; PTQueueItem = ^TQueueItem; TQueueItem = object item: T; next: PTQueueItem; constructor create(value: T); end; TQueue = object constructor create; destructor destroy; procedure get(var value: T); procedure put(const value: T); function is_empty: boolean; private front, back: PTQueueItem; end; constructor TQueueItem.create(value: T); begin item := value; next := nil; end; constructor TQueue.create; begin front := nil; back := nil; end; destructor TQueue.destroy; var value: T; begin while not is_empty do get(value); end; procedure TQueue.get(var value: T); var pt: PTQueueItem; begin if is_empty then begin writeln('Trying to Get from the empty queue !'); halt(101) end; pt := front; front := front^.next; value := pt^.item; dispose(pt); end; procedure TQueue.put(const value: T); var pt: PTQueueItem; begin pt := new(PTQueueItem, create(value)); if is_empty then begin front := pt; back := pt; end else begin back^.next := pt; back := pt; end; end; function TQueue.is_empty: boolean; begin is_empty := (front = nil) end; type status = ( _busy = 0, _waiting, _vacation ); PTCasher = ^TCasher; TCasher = object state: status; work_time, ID: integer; { *** const *** } working_time, this_client: integer; public constructor create(_id, _work: integer); function handle(var changed: integer): status; procedure next; end; constructor TCasher.create(_id, _work: integer); begin ID := _id; work_time := _work; state := _waiting; working_time := work_time; end; { Функция Handle вызывается ежеминутно из Менеджера, и возвращает текущее состояние Кассира (как результат) и через параметр changed - признак того, что состояние изменилось } function TCasher.handle(var changed: integer): status; begin changed := 0; { Если кассир работал с Клиентом, то уменьшаем оставшееся время работы с ним, и одновременно уменьшаем счетчик времени до очередного отдыха Кассира } if state = _busy then begin dec(this_client); dec(working_time); { Если this_client = 0, это значит, что с этим Rлиентом мы закончили работать, статус изменился как минимум на "ожидание", кроме этого - устанавливаем признак того, что состояние изменилось } if this_client = 0 then begin state := _waiting; changed := 1; end; end; { Если Кассир отдыхал - то уменьшаем время отдыха (когда кассир начинает отдыхать, в переменную working_time записывается _отрицательное_ значение, и постепенно увеличивается, чтобы не вводить специальную переменную) } if state = _vacation then begin inc(working_time); { Если working_time = 0, то отдых закончился, и переходим в "ожидание", с установкой признака изменения состояния, и кроме этого, устанавливаем положительное время работы ДО следующего отдыха } if working_time > 0 then begin state := _waiting; working_time := work_time; changed := 1; end; end; { Если после всех предыдущих проверок состояние = "ожидание" ... } if state = _waiting then begin { ... если не было изменения состояния - уменьшаем время до очередного отдыха (если это условие не поставить, то время может уменьшится дважды - один раз при переходе от _busy к _waiting, и второй раз - здесь, а это делать нежелательно) } if changed = 0 then dec(working_time); { ... и, наконец, проверяем не пришло ли время отдохнуть (это уже проверяется независимо от того, было ли изменение состояния, т.е. если кассир на этой минуте закончил работать с клиентом, и ему уже пора отдыхать, он должен начать отдыхать...), и если пришло, то ставим отрицательное время работы равное константе N6, и сигнализируем об изменении состояния } if working_time <= 0 then begin state := _vacation; working_time := -n6; changed := 1; end; end; handle := state; end; { Эта функция просто напросто имитирует подход клиента к кассиру: переводит кассира в состояние "занят" и устанавливает время обслуживания данного клиента... } procedure TCasher.next; begin state := _busy; this_client := f2; end; type TManager = object { Менеждер включает в себя очередь "посетителей", массив "Кассиров" ... } queue: TQueue; cash_workers: array[0 .. pred(n)] of PTCasher; { ... текущее время (0 - при начале работы банка), и время после прихода последнего посетителя } current_time, after_prev_enter: integer; public constructor create; destructor destroy; procedure run; end; constructor TManager.create; var i: integer; begin current_time := 0; after_prev_enter := 0; { инициализируем "Кассиров" } for i := 0 to pred(n) do cash_workers[i] := new(PTCasher, create(i + 1, n5)); end; { В деструкторе - "закрываем кассы" } destructor TManager.destroy; var i: integer; begin for i := 0 to pred(n) do dispose(cash_workers[i]); end; { вот в этой функции как раз и происходит вся имитация: } procedure TManager.run; const { эта _статическая_ переменная - для хранения количества пришедших посетителей (используется для присвоения ID клиенту) } client_entered: integer = 0; var i: integer; status_changed: integer; curr_status: status; client: PTClient; begin { Все операции продолжаются до тех пор, пока не вышло время работы банка, и потом, пока в очереди есть хотя бы один посетитель (не выгонять же, правда?) } while (current_time <= nT) or (not queue.is_empty) do begin { Перебираем всех кассиров, и для каждого ... } for i := 0 to pred(n) do begin { предполагаем, что его статус НЕ изменился } status_changed := 0; { получаем статус кассира обращением к ЕГО методу } curr_status := cash_workers[i]^.Handle(status_changed); { если статус изменился - отобразить этот факт в статистике (какой кассир, как изменился статус, когда изменился - в какое время) } if status_changed <> 0 then { statistics } case curr_status of _vacation: writeln('casher #', i, ' goes to rest at:: ', current_time); _waiting: writeln('casher #', i, ' awaiting at:: ', current_time); end; { если сейчас проверяемый кассир ожидает клиента, то ... } if curr_status = _waiting then if not queue.is_empty then begin { ... вытянуть посетителя из очереди (если там кто-то есть, разумеется) } queue.get(client); { добавить в статистику, что такой-то посетитель направлен к такому-то кассиру в такое-то время } writeln('client #', client^.get_id, ' sent to casher #', i, ' at:: ', current_time); { и, собственно, направить клиента к кассиру } cash_workers[i]^.next; dispose(client); end; end; { здесь (после каждого прохода по всем кассирам) проверяем, не закончилось ли время работы банка ... } if current_time <= nT then begin { если еще НЕ закончилось - то увеличиваем время, прошедшее после прихода последнего посетителя } inc(after_prev_enter); { и если это время больше того, которое должно быть по формуле (интервал между приходом посетителей) } if after_prev_enter > f1 then begin { то имитируем приход очередного клиента: он только что пришел, т.е. время обнуляем ... } after_prev_enter := 0; inc(client_entered); { вызываем конструктор TClient, перед этим увеличиваем общий счетчик посетителей, и передаем его значение как ID в конструктор (чтобы ВСЕ посетители хоть чем-то различались) } client := new(PTClient, create(client_entered)); { добавляем вновь прибывшего посетителя в очередь } queue.put(client); end; { если же время работы банка уже закончилось, то новые посетители НЕ будут добавляться в очередь, а те, кто пришел раньше, и уже стоит в очереди, будут обслужены, и только тогда этот цикл закончится... } end; { прибавляем к "банковскому" времени минуту, и начинаем заново опрашивать кассиров } inc(current_time); end; writeln(client_entered); end; var bank: TManager; begin bank.create; bank.run; bank.destroy; end.
Исходник: bank.pas