Відповіді:
Замки використовуються для взаємного виключення. Коли ви хочете переконатися, що фрагмент коду є атомним, поставте навколо нього замок. Ви можете теоретично використовувати двійковий семафор для цього, але це особливий випадок.
Семафори та змінні умови будуються на основі взаємного виключення, що забезпечується блокуваннями, і використовуються для забезпечення синхронізованого доступу до спільних ресурсів. Їх можна використовувати для подібних цілей.
Загальна змінна умова зазвичай використовується для уникнення зайнятого очікування (циклічного циклу під час перевірки стану) під час очікування доступу до ресурсу. Наприклад, якщо у вас є потік (або кілька потоків), який не може продовжуватись вперед, поки черга не буде порожньою, підхід із зайнятим очікуванням буде просто робити щось на кшталт:
//pseudocode
while(!queue.empty())
{
sleep(1);
}
Проблема з цим полягає в тому, що ви витрачаєте процесорний час, повторюючи умови поточної перевірки. Чому б замість цього не було змінної синхронізації, яку можна сигналізувати, щоб повідомити потоку про те, що ресурс доступний?
//pseudocode
syncVar.lock.acquire();
while(!queue.empty())
{
syncVar.wait();
}
//do stuff with queue
syncVar.lock.release();
Імовірно, у вас буде десь інша тема, яка витягує речі з черги. Коли черга порожня, вона може зателефонувати, syncVar.signal()
щоб прокинутись випадковою ниткою, яка сидить уві сні syncVar.wait()
(або зазвичай також є signalAll()
або broadcast()
метод, щоб прокинути всі потоки, які чекають).
Я зазвичай використовую такі змінні синхронізації, коли я маю одну або кілька потоків, які чекають на одному конкретному умові (наприклад, щоб черга була порожньою).
Семафори можна використовувати аналогічно, але я думаю, що їх краще використовувати, коли у вас є спільний ресурс, який може бути доступним та недоступним на основі деякої цілої кількості доступних речей. Семафори корисні для виробників / споживачів, коли виробники виділяють ресурси, а споживачі їх споживають.
Подумайте, чи був у вас автомат з газованою содою. Є лише одна содова машина, і це спільний ресурс. У вас є одна нитка - це постачальник (виробник), який відповідає за збереження машини на складі, і N ниток - покупці (споживачі), які хочуть дістати газовані напої з машини. Кількість газованих напоїв у машині - це ціле значення, яке призведе до нашого семафору.
Кожен покупець (споживач), який надходить на содову машину, закликає down()
метод семафору взяти соду. Це візьме соду з машини і зменшить кількість доступних газованих напоїв на 1. Якщо є газовані напої, код просто продовжує проходити повз down()
заяву без проблем. Якщо немає газованої речовини, нитка буде спати тут, чекаючи повідомлення про те, коли сода знову буде доступна (коли в машині буде більше газованих напоїв).
Потік постачальника (виробника), по суті, буде чекати, поки машина соди буде порожньою. Постачальник отримує сповіщення, коли останній соди виймається з машини (і один або кілька споживачів потенційно чекають, щоб вивезти газовану воду). Постачальник відновлює машину соди up()
методом семафору , наявна кількість газованих напоїв збільшуватиметься кожного разу, і тим самим очікуючі споживачі, які очікують, отримуватимуть повідомлення про наявність більшої кількості соди.
Методи wait()
і signal()
змінної синхронізації, як правило, приховані в семафорі down()
та up()
операціях.
Звичайно, між двома варіантами є перекриття. Існує багато сценаріїв, коли семафор або змінна умова (або набір змінних умов) можуть обидва служити вашим цілям. І семафори, і змінні умови асоціюються з об'єктом блокування, який вони використовують для підтримки взаємного виключення, але потім вони надають додаткову функціональність поверх блокування для синхронізації виконання потоку. В основному, ви повинні визначити, який з них має найбільш сенс для вашої ситуації.
Це не обов'язково найбільш технічний опис, але саме так це має сенс у моїй голові.
Розкриємо, що знаходиться під кришкою.
Умовна змінна, по суті, є чергою очікування , яка підтримує операції блокування-очікування та пробудження, тобто ви можете помістити потік у чергу очікування та встановити її стан на БЛОК, і отримати нитку з неї та встановити її стан НАСТОЯТИЙ.
Зауважте, що для використання умовної змінної потрібні два інші елементи:
Потім протокол стає,
Семафор - це по суті лічильник + мутекс + черга очікування. І його можна використовувати як без зовнішніх залежностей. Ви можете використовувати його як мутекс або як умовну змінну.
Тому семафор можна розглядати як більш досконалу структуру, ніж умовну змінну, тоді як остання є більш легкою та гнучкою.
Семафори можна використовувати для реалізації виключного доступу до змінних, однак вони призначені для синхронізації. Mutexes, з іншого боку, має семантику, яка суворо пов'язана з взаємним виключенням: розблокувати її може лише той процес, який заблокував ресурс.
На жаль, ви не можете здійснити синхронізацію з мютексами, тому ми маємо змінні умови. Також зауважте, що за допомогою змінних умов ви можете розблокувати всі потоки очікування в одну мить за допомогою розблокування трансляції. Це неможливо зробити семафорами.
змінні семафору та стану дуже схожі і використовуються здебільшого для одних і тих же цілей. Однак є незначні відмінності, які можуть зробити одну переважнішою. Наприклад, для здійснення бар'єрної синхронізації ви не зможете використовувати семафор. Але змінна умова є ідеальною.
Бар'єрна синхронізація - це тоді, коли ви хочете, щоб усі ваші потоки чекали, поки всі досягли певної частини функції потоку. це може бути реалізовано, маючи статичну змінну, яка спочатку є значенням загальних потоків, зменшених кожним потоком, коли вона досягає цього бар'єру. це означає, що ми хочемо, щоб кожна нитка спала, поки не надійде остання. Семафор зробить все навпаки! за допомогою семафору кожен потік продовжуватиме працювати, а останній потік (який встановить значення семафору в 0) перейде до сну.
змінна умова з іншого боку, є ідеальною. коли кожен потік потрапляє до бар'єру, ми перевіряємо, чи є наш статичний лічильник нульовим. якщо ні, то ми встановлюємо потік для сну з функцією очікування змінної умови. коли останній потік потрапить на бар'єр, значення лічильника зменшиться до нуля, і цей останній потік викликає функцію сигналу змінної умови, яка прокине всі інші потоки!
Я зберігаю змінні стану під час синхронізації монітора. Я зазвичай бачив семафори та монітори як два різні стилі синхронізації. Між двома різницями щодо того, скільки даних про стан притаманне, і як ви хочете моделювати код - але насправді не існує жодної проблеми, яку можна вирішити однією, а не іншою.
Я схильний кодувати до форми монітора; у більшості мов, на яких я працюю, зводиться до мутексів, змінних умов та деяких змінних резервного стану. Але семафори теж зробили б свою роботу.
Ті mutex
і conditional variables
успадковуються від semaphore
.
mutex
, в semaphore
використанні двох станів: 0, 1condition variables
в semaphore
лічильнику використовує.Вони схожі на синтаксичний цукор