Ця стаття не містить . (липень 2013) |
Задача постачальника-споживача (англ. producer-consumer problem), також відома як задача обмеженого буфера (англ. bounded-buffer problem) - це класичний приклад задачі синхронізації кількох процесів. Задача описує два процеси, постачальник і споживач, які спільно використовують буфер встановленого розміру. Завданням постачальника є створення фрагменту даних, запис його до буфера і повторення цих дій раз за разом. Одночасно з цим споживач споживає дані (тобто, видаляє їх з буфера) по одному фрагменту за раз. Задача полягає в тому, щоб не дати постачальнику записати дані, коли буфер повний, а споживачу не дати видалити дані з порожнього буфера.
Розв'язком може бути перехід у стан очікування, якщо буфер повний, або ігнорування даних в такому випадку (сценарій повільного споживача). Коли споживач видалить наступні дані з буфера, він сповіщає про це постачальника, який починає заповнювати буфер знов. Таким самим чином споживач може перейти в режим очікування, якщо буфер виявиться порожнім. Щойно постачальник запише наступні дані він сповіщує про це споживача. Розв'язання задачі можна досягнути за допомогою взаємодії між процесами, зазвичай використовуються семафори. Невідповідний підхід може спричинити взаємне блокування, коли обидва процеси опиняться в стані очікування. Задача може бути узагальнена на випадок багатьох постачальників і споживачів.
Реалізація
Непридатна реалізація
Наведений розв’язок потенційно спричиняє стан гонитви. Використовуються дві бібліотечні функції, sleep і wakeup. Процес, що викликав sleep, блокується допоки інший процес не розблокує його через використання wakeup. Глобальна зміння itemCount містить кількість записів у буфері.
int itemCount; procedure producer() { while (true) { item = produceItem(); if (itemCount == BUFFER_SIZE) { sleep(); } putItemIntoBuffer(item); itemCount = itemCount + 1; if (itemCount == 1) { wakeup(consumer); } } } procedure consumer() { while (true) { if (itemCount == 0) { sleep(); } item = removeItemFromBuffer(); itemCount = itemCount - 1; if (itemCount == BUFFER_SIZE - 1) { wakeup(producer); } consumeItem(item); } }
Недолік цього розв'язку в тому, що він робить можливим стан гонитви, який призводить до взаємного блокування. Уявімо наступний сценарій:
- Споживач щойно прочитав змінну itemCount, встановив, що вона дорівнює нулю, отже має увійти в if-блок.
- Просто перед викликом sleep споживач переривається і запускається постачальник.
- Постачальник створює запис, записує його до буфера і збільшує itemCount на одиницю.
- Оскільки буфер перед цим був порожнім, постачальник пробує розбудити споживача.
- Споживач не перебуває у стані сну, тож виклик wakeup не має наслідків. Споживач переходить до інструкції sleep, засинає і вже ніколи не буде пробуджений, оскільки постачальник будить споживача лише тоді, коли itemCount дорівнює 1.
- Постачальник працюватиме до заповнення буфера, після чого також засне.
Обидва процеси спатимуть довічно, що є взаємним блокуванням.
Альтернативне доведення непридатності розв’язку полягяє в тому, що якщо мова програмування не визначає способів одночасного доступу до спільних змінних (наразі itemCount) без використання механізмів синхронізації, рішення є незадовільним вже з самої цієї причини, навіть без необхідності явного доказу можливості настання стану гонитви.
Використання семафорів
Семафори розв'язують питання неспрацювання виклику wakeup. В рішенні нижче використовуються два семафори, fillCount і emptyCount. fillCount — це кількість доступних записів у буфері, emptyCount – кількість вільних місць в буфері. fillCount збільшується, а emptyCount зменшується коли створюється новий запис. Якщо постачальник намагається зменшити emptyCount, коли його значення нуль, він засинає. Коли споживач зчитує наступний запис, emptyCount збільшується і постачальник прокидається. Споживач працює подібним чином.
semaphore fillCount = 0; // фрагментів записано semaphore emptyCount = BUFFER_SIZE; // залишилось місця procedure producer() { while (true) { item = produceItem(); down(emptyCount); putItemIntoBuffer(item); up(fillCount); } } procedure consumer() { while (true) { down(fillCount); item = removeItemFromBuffer(); up(emptyCount); consumeItem(item); } }
Наведений розв'язок працює добре в разі, коли присутні один постачальник та один споживач. Однак у випадку з багатьма постачальниками або багатьма споживачами, що використовують спільну пам’ять для буферу, цей розв'язок призводить до стану гонитви, що може призвести до читання з однієї або писання до однієї комірки кількома процесами одночасно. Для розуміння, як це можливо, уявімо як може бути реалізована підпрограма putItemIntoBuffer(). Вона може містити дві дії, перша це визначення доступного слоту і друга це запис в нього. ця процедура може виконуватись одночасно декількома процесами, що уможливлює такий перебіг подій:
- Два постачальники зменшують emptyCount
- Один з них визначає наступну порожню комірку в буфері
- Другий постачальник визначає наступну порожню комірку і отримує однаковий з першим результат
- Обидва пишуть в одну комірку
Для подалання цієї проблеми, ми маємо переконатись, що лиш один постачальник виконує putItemIntoBuffer() в цей час. Інакше кажучи, ми потребуємо спосіб для виконання критичної секції із взаємним виключенням. Для успішного виконання цієї задачі ми використаємо двійковий семафор, відомий як м'ютекс. Через те, що значення м'ютекса може бути або нуль, або одиниця, тільки один процес може виконуватись між down(mutex) і up(mutex). Розв'язок для багатьох постачальників і споживачів наведений нижче.
semaphore mutex = 1; semaphore fillCount = 0; semaphore emptyCount = BUFFER_SIZE; procedure producer() { while (true) { item = produceItem(); down(emptyCount); down(mutex); putItemIntoBuffer(item); up(mutex); up(fillCount); } } procedure consumer() { while (true) { down(fillCount); down(mutex); item = removeItemFromBuffer(); up(mutex); up(emptyCount); consumeItem(item); } }
Зауважте, що черговість збільшення та зменшення різних семафорів важлива: зміна черговості може спричинити взаємне блокування.
Використання моніторів
Наступний псевдокод наводить розв'язок задачі постачальника-споживача із використанням моніторів. Через прихованість взаємного виключення в випадку використання моніторів, додаткові зусилля для захисту критичної секції не потрібні. Інакше кажучи, розв'язок наведений нижче працює з будь-якою кількістю постачальників і споживачів без змін. Варто також зазначити, що використання моніторів робить можливість гонитви значно меншою порівняно з використанням семафорів.
monitor ProducerConsumer { int itemCount condition full; condition empty; procedure add(item) { while (itemCount == BUFFER_SIZE) { wait(full); } putItemIntoBuffer(item); itemCount = itemCount + 1; if (itemCount == 1) { notify(empty); } } procedure remove() { while (itemCount == 0) { wait(empty); } item = removeItemFromBuffer(); itemCount = itemCount - 1; if (itemCount == BUFFER_SIZE - 1) { notify(full); } return item; } } procedure producer() { while (true) { item = produceItem() ProducerConsumer.add(item) } } procedure consumer() { while (true) { item = ProducerConsumer.remove() consumeItem() } }
Зауважте використання while в коді згори у випадку перевірки на порожність або заповнення буфера. Якщо while замінити на if, забагато записів може бути записано в буфер або може відбутися спроба видалення з порожнього буфера.
Без семафорів або моніторів
Задача постачальника-споживача, особливо у випадку одного постачальника та одного споживача, має значну подібність до FIFO або каналу зв'язку. Шаблон постачальник споживач може забезпечити високо дієву передачу даних без покладання на семафори, м'ютекси або монітори для передачі даних. Використання цих примітивів може вплинути на швидкодію через високу вартість здійснення. Базовий код на C подано нижче. Зауважте, що:
- Атомарний читати-змінювати-записувати доступ до спільних змінних не використовується: кожна з двох змінних лічильників оновлюється лише одним потоком.
- Цей приклад не присипляє потоки, хоча це може бути вдалим рішенням в деяких системах.
volatile unsigned int produceCount, consumeCount; TokenType buffer[BUFFER_SIZE]; void producer(void) { while (1) { while (produceCount - consumeCount == BUFFER_SIZE) sched_yield(); // буфер повний buffer[produceCount % BUFFER_SIZE] = produceToken(); produceCount += 1; } } void consumer(void) { while (1) { while (produceCount - consumeCount == 0) sched_yield(); // буфер порожній consumeToken( buffer[consumeCount % BUFFER_SIZE]); consumeCount += 1; } }
Вікіпедія, Українська, Україна, книга, книги, бібліотека, стаття, читати, завантажити, безкоштовно, безкоштовно завантажити, mp3, відео, mp4, 3gp, jpg, jpeg, gif, png, малюнок, музика, пісня, фільм, книга, гра, ігри, мобільний, телефон, android, ios, apple, мобільний телефон, samsung, iphone, xiomi, xiaomi, redmi, honor, oppo, nokia, sonya, mi, ПК, web, Інтернет
Cya stattya ne mistit posilan na dzherela Vi mozhete dopomogti polipshiti cyu stattyu dodavshi posilannya na nadijni avtoritetni dzherela Material bez dzherel mozhe buti piddano sumnivu ta vilucheno lipen 2013 Zadacha postachalnika spozhivacha angl producer consumer problem takozh vidoma yak zadacha obmezhenogo bufera angl bounded buffer problem ce klasichnij priklad zadachi sinhronizaciyi kilkoh procesiv Zadacha opisuye dva procesi postachalnik i spozhivach yaki spilno vikoristovuyut bufer vstanovlenogo rozmiru Zavdannyam postachalnika ye stvorennya fragmentu danih zapis jogo do bufera i povtorennya cih dij raz za razom Odnochasno z cim spozhivach spozhivaye dani tobto vidalyaye yih z bufera po odnomu fragmentu za raz Zadacha polyagaye v tomu shob ne dati postachalniku zapisati dani koli bufer povnij a spozhivachu ne dati vidaliti dani z porozhnogo bufera Rozv yazkom mozhe buti perehid u stan ochikuvannya yaksho bufer povnij abo ignoruvannya danih v takomu vipadku scenarij povilnogo spozhivacha Koli spozhivach vidalit nastupni dani z bufera vin spovishaye pro ce postachalnika yakij pochinaye zapovnyuvati bufer znov Takim samim chinom spozhivach mozhe perejti v rezhim ochikuvannya yaksho bufer viyavitsya porozhnim Shojno postachalnik zapishe nastupni dani vin spovishuye pro ce spozhivacha Rozv yazannya zadachi mozhna dosyagnuti za dopomogoyu vzayemodiyi mizh procesami zazvichaj vikoristovuyutsya semafori Nevidpovidnij pidhid mozhe sprichiniti vzayemne blokuvannya koli obidva procesi opinyatsya v stani ochikuvannya Zadacha mozhe buti uzagalnena na vipadok bagatoh postachalnikiv i spozhivachiv RealizaciyaNepridatna realizaciya Navedenij rozv yazok potencijno sprichinyaye stan gonitvi Vikoristovuyutsya dvi bibliotechni funkciyi sleep i wakeup Proces sho viklikav sleep blokuyetsya dopoki inshij proces ne rozblokuye jogo cherez vikoristannya wakeup Globalna zminnya itemCount mistit kilkist zapisiv u buferi int itemCount procedure producer while true item produceItem if itemCount BUFFER SIZE sleep putItemIntoBuffer item itemCount itemCount 1 if itemCount 1 wakeup consumer procedure consumer while true if itemCount 0 sleep item removeItemFromBuffer itemCount itemCount 1 if itemCount BUFFER SIZE 1 wakeup producer consumeItem item Nedolik cogo rozv yazku v tomu sho vin robit mozhlivim stan gonitvi yakij prizvodit do vzayemnogo blokuvannya Uyavimo nastupnij scenarij Spozhivach shojno prochitav zminnu itemCount vstanoviv sho vona dorivnyuye nulyu otzhe maye uvijti v if blok Prosto pered viklikom sleep spozhivach pererivayetsya i zapuskayetsya postachalnik Postachalnik stvoryuye zapis zapisuye jogo do bufera i zbilshuye itemCount na odinicyu Oskilki bufer pered cim buv porozhnim postachalnik probuye rozbuditi spozhivacha Spozhivach ne perebuvaye u stani snu tozh viklik wakeup ne maye naslidkiv Spozhivach perehodit do instrukciyi sleep zasinaye i vzhe nikoli ne bude probudzhenij oskilki postachalnik budit spozhivacha lishe todi koli itemCount dorivnyuye 1 Postachalnik pracyuvatime do zapovnennya bufera pislya chogo takozh zasne Obidva procesi spatimut dovichno sho ye vzayemnim blokuvannyam Alternativne dovedennya nepridatnosti rozv yazku polyagyaye v tomu sho yaksho mova programuvannya ne viznachaye sposobiv odnochasnogo dostupu do spilnih zminnih narazi itemCount bez vikoristannya mehanizmiv sinhronizaciyi rishennya ye nezadovilnim vzhe z samoyi ciyeyi prichini navit bez neobhidnosti yavnogo dokazu mozhlivosti nastannya stanu gonitvi Vikoristannya semaforiv Semafori rozv yazuyut pitannya nespracyuvannya vikliku wakeup V rishenni nizhche vikoristovuyutsya dva semafori fillCount i emptyCount fillCount ce kilkist dostupnih zapisiv u buferi emptyCount kilkist vilnih misc v buferi fillCount zbilshuyetsya a emptyCount zmenshuyetsya koli stvoryuyetsya novij zapis Yaksho postachalnik namagayetsya zmenshiti emptyCount koli jogo znachennya nul vin zasinaye Koli spozhivach zchituye nastupnij zapis emptyCount zbilshuyetsya i postachalnik prokidayetsya Spozhivach pracyuye podibnim chinom semaphore fillCount 0 fragmentiv zapisano semaphore emptyCount BUFFER SIZE zalishilos miscya procedure producer while true item produceItem down emptyCount putItemIntoBuffer item up fillCount procedure consumer while true down fillCount item removeItemFromBuffer up emptyCount consumeItem item Navedenij rozv yazok pracyuye dobre v razi koli prisutni odin postachalnik ta odin spozhivach Odnak u vipadku z bagatma postachalnikami abo bagatma spozhivachami sho vikoristovuyut spilnu pam yat dlya buferu cej rozv yazok prizvodit do stanu gonitvi sho mozhe prizvesti do chitannya z odniyeyi abo pisannya do odniyeyi komirki kilkoma procesami odnochasno Dlya rozuminnya yak ce mozhlivo uyavimo yak mozhe buti realizovana pidprograma putItemIntoBuffer Vona mozhe mistiti dvi diyi persha ce viznachennya dostupnogo slotu i druga ce zapis v nogo cya procedura mozhe vikonuvatis odnochasno dekilkoma procesami sho umozhlivlyuye takij perebig podij Dva postachalniki zmenshuyut emptyCount Odin z nih viznachaye nastupnu porozhnyu komirku v buferi Drugij postachalnik viznachaye nastupnu porozhnyu komirku i otrimuye odnakovij z pershim rezultat Obidva pishut v odnu komirku Dlya podalannya ciyeyi problemi mi mayemo perekonatis sho lish odin postachalnik vikonuye putItemIntoBuffer v cej chas Inakshe kazhuchi mi potrebuyemo sposib dlya vikonannya kritichnoyi sekciyi iz vzayemnim viklyuchennyam Dlya uspishnogo vikonannya ciyeyi zadachi mi vikoristayemo dvijkovij semafor vidomij yak m yuteks Cherez te sho znachennya m yuteksa mozhe buti abo nul abo odinicya tilki odin proces mozhe vikonuvatis mizh down mutex i up mutex Rozv yazok dlya bagatoh postachalnikiv i spozhivachiv navedenij nizhche semaphore mutex 1 semaphore fillCount 0 semaphore emptyCount BUFFER SIZE procedure producer while true item produceItem down emptyCount down mutex putItemIntoBuffer item up mutex up fillCount procedure consumer while true down fillCount down mutex item removeItemFromBuffer up mutex up emptyCount consumeItem item Zauvazhte sho chergovist zbilshennya ta zmenshennya riznih semaforiv vazhliva zmina chergovosti mozhe sprichiniti vzayemne blokuvannya Vikoristannya monitoriv Nastupnij psevdokod navodit rozv yazok zadachi postachalnika spozhivacha iz vikoristannyam monitoriv Cherez prihovanist vzayemnogo viklyuchennya v vipadku vikoristannya monitoriv dodatkovi zusillya dlya zahistu kritichnoyi sekciyi ne potribni Inakshe kazhuchi rozv yazok navedenij nizhche pracyuye z bud yakoyu kilkistyu postachalnikiv i spozhivachiv bez zmin Varto takozh zaznachiti sho vikoristannya monitoriv robit mozhlivist gonitvi znachno menshoyu porivnyano z vikoristannyam semaforiv monitor ProducerConsumer int itemCount condition full condition empty procedure add item while itemCount BUFFER SIZE wait full putItemIntoBuffer item itemCount itemCount 1 if itemCount 1 notify empty procedure remove while itemCount 0 wait empty item removeItemFromBuffer itemCount itemCount 1 if itemCount BUFFER SIZE 1 notify full return item procedure producer while true item produceItem ProducerConsumer add item procedure consumer while true item ProducerConsumer remove consumeItem Zauvazhte vikoristannya while v kodi zgori u vipadku perevirki na porozhnist abo zapovnennya bufera Yaksho while zaminiti na if zabagato zapisiv mozhe buti zapisano v bufer abo mozhe vidbutisya sproba vidalennya z porozhnogo bufera Bez semaforiv abo monitoriv Zadacha postachalnika spozhivacha osoblivo u vipadku odnogo postachalnika ta odnogo spozhivacha maye znachnu podibnist do FIFO abo kanalu zv yazku Shablon postachalnik spozhivach mozhe zabezpechiti visoko diyevu peredachu danih bez pokladannya na semafori m yuteksi abo monitori dlya peredachi danih Vikoristannya cih primitiviv mozhe vplinuti na shvidkodiyu cherez visoku vartist zdijsnennya Bazovij kod na C podano nizhche Zauvazhte sho Atomarnij chitati zminyuvati zapisuvati dostup do spilnih zminnih ne vikoristovuyetsya kozhna z dvoh zminnih lichilnikiv onovlyuyetsya lishe odnim potokom Cej priklad ne prisiplyaye potoki hocha ce mozhe buti vdalim rishennyam v deyakih sistemah volatile unsigned int produceCount consumeCount TokenType buffer BUFFER SIZE void producer void while 1 while produceCount consumeCount BUFFER SIZE sched yield bufer povnij buffer produceCount BUFFER SIZE produceToken produceCount 1 void consumer void while 1 while produceCount consumeCount 0 sched yield bufer porozhnij consumeToken buffer consumeCount BUFFER SIZE consumeCount 1