Хоча мютекс може використовуватися для вирішення інших проблем, основною причиною їх існування є забезпечення взаємного виключення і тим самим вирішення того, що називається перегоном. Коли два (або більше) потоків або процесів намагаються отримати доступ до однієї змінної одночасно, у нас є потенціал для умови перегонів. Розглянемо наступний код
//somewhere long ago, we have i declared as int
void my_concurrently_called_function()
{
i++;
}
Внутрішня функція цієї функції виглядає так просто. Це лише одне твердження. Однак типовим еквівалентом мови псевдоскладань може бути:
load i from memory into a register
add 1 to i
store i back into memory
Оскільки всі еквівалентні інструкції з мови збірки необхідні для виконання операції збільшення на i, ми говоримо, що збільшення i є неатмосферною операцією. Атомна операція - це та, яка може бути завершена на апаратному забезпеченні з гарантією того, що вона не буде перервана після того, як виконання інструкції розпочато. Збільшення i складається з ланцюга з 3 атомних інструкцій. У паралельній системі, де декілька потоків викликають функцію, проблеми виникають, коли нитка читає або записує в невідповідний час. Уявіть, що у нас є два потоки, які працюють simultaneoulsy, і один викликає функцію відразу за іншою. Скажімо також, що ми ініціалізували до 0. Також припустимо, що у нас є безліч регістрів і що два потоки використовують абсолютно різні регістри, тож не буде зіткнень. Фактичним часом цих подій може бути:
thread 1 load 0 into register from memory corresponding to i //register is currently 0
thread 1 add 1 to a register //register is now 1, but not memory is 0
thread 2 load 0 into register from memory corresponding to i
thread 2 add 1 to a register //register is now 1, but not memory is 0
thread 1 write register to memory //memory is now 1
thread 2 write register to memory //memory is now 1
Що сталося, це те, що у нас збільшуються дві нитки i одночасно, наша функція викликається двічі, але результат не відповідає цьому факту. Схоже, функцію викликали лише один раз. Це тому, що атомність "порушена" на машинному рівні, тобто нитки можуть перебивати один одного або працювати разом у неправильний час.
Нам потрібен механізм для вирішення цього питання. Нам потрібно нав'язати деяке впорядкування до вищезазначених інструкцій. Один загальний механізм - блокувати всі потоки, крім одного. Pthread mutex використовує цей механізм.
Будь-який потік, який повинен виконувати деякі рядки коду, які можуть одночасно небезпечно змінювати спільні значення іншими потоками (використовуючи телефон для спілкування зі своєю дружиною), слід спочатку зробити замовлення на мьютекс. Таким чином, будь-який потік, який потребує доступу до спільних даних, повинен проходити через блокування mutex. Тільки тоді потік зможе виконати код. Цей розділ коду називають критичним розділом.
Після того, як нитка виконала критичну секцію, вона повинна звільнити замок на мютекс, щоб інша нитка могла придбати замок на мютекс.
Концепція наявності мутексу здається дещо дивною, коли ми розглядаємо людей, які прагнуть виключного доступу до реальних фізичних об'єктів, але при програмуванні ми повинні бути навмисними. Одночасно нитки та процеси не мають соціального та культурного виховання, яке ми виконуємо, тому ми повинні змусити їх добре обмінюватися даними.
Отже, технічно кажучи, як працює мютекс? Чи не страждає він від тих самих умов гонки, про які ми згадували раніше? Чи не pthread_mutex_lock () трохи складніший, ніж простий приріст змінної?
Технічно кажучи, нам потрібна деяка апаратна підтримка, щоб допомогти нам. Конструктори апаратних засобів дають нам інструкції з машин, які роблять більше, ніж одне, але гарантовано є атомними. Класичним прикладом такої інструкції є тест-набір (TAS). Намагаючись придбати блокування ресурсу, ми можемо використовувати TAS, щоб перевірити, чи є значення в пам'яті 0. Якщо це так, це буде нашим сигналом про те, що ресурс використовується, і ми нічого не робимо (або більш точно , ми чекаємо деякого механізму. Mtex pthreads помістить нас у спеціальну чергу в операційній системі та повідомить нас, коли ресурс стане доступним. Дублюючі системи можуть вимагати від нас жорсткого циклу віджимання, перевіряючи стан знову і знову). . Якщо значення в пам'яті не дорівнює 0, TAS встановлює розташування на щось інше, ніж 0, не використовуючи жодних інших вказівок. Це ' подібне поєднанню двох інструкцій по збірці в 1, щоб надати нам атомність. Таким чином, тестування та зміна значення (якщо зміна доречна) не може бути перервана після її початку. На основі такої інструкції ми можемо будувати мутекси.
Примітка: деякі розділи можуть виглядати подібними до попередньої відповіді. Я прийняв його запрошення редагувати, він віддав перевагу оригінальному способу, так що я тримаю те, що було у мене, просякнуте трохи його багатослівністю.