6. Блокування файлу

При інтенсивному обміні даними з файлами в мультизадачних операційних системах виникає питання синхронізації операцій читання/запису між процесами. Наприклад, нехай у нас є кілька "процесів-письменників" та один "процес-читач". Необхідно, щоб в одиницю часу до файлу мав доступ лише один процес-письменник, а решта на цей час як би "підвисала", чекаючи своєї черги. Це потрібно, наприклад, щоб дані від кількох процесів не перемішувалися у файлі, а слідували блок за блоком. Як ми можемо досягти цього?

Тут на допомогу приходить функція flock(), яка встановлює так зване "рекомендаційне блокування" для файлу. Це означає, що блокування доступу здійснюється не лише на рівні ядра системи, але в рівні програми.

Рекомендоване блокування можна порівняти з перехрестям, на якому встановився досить жвавий рух, регульований світлофором. Коли горить червоний, одні стоять, а інші проїжджають. У принципі, будь-яка машина може, так би мовити, проїхати всупереч правилам дорожнього руху, не чекаючи зеленого сигналу, але в такому разі можливі аварії.

Рекомендоване блокування працює так само. А саме, процеси, які їй користуються, будуть працювати з файлом, що розділяється правильно, а інші ... як-небудь так будуть, поки не відбудеться "зіткнення".

З іншого боку, "жорстке блокування" (яке в PHP не підтримується) подібне до шлагбауму: ніхто не зможе проїхати, поки його не піднімуть.

Єдина функція, яка займається керуванням блокуванням PHP, називається flock().

bool flock(int $f, int $operation [, int& $wouldblock])

Функція встановлює для вказаного відкритого дескриптора файлу $f режим блокування, який хотів биотримати поточний процес. Цей режим визначається аргументом $operation і може бути однією з наступних констант:

LOCK_SH (або 1) — блокування, що розділяється;

LOCK_EX (або 2) — виняткове блокування;

LOCK_UN (або 3) — зняти блокування;

LOCK_NB (або 4) — цю константу потрібно додати до однієї з попередніх, якщо ви не хочете, щоб програма "підвисала" на flock() в очікуванні своєї черги, а одразу повертала керування.

У випадку, якщо був затребуваний режим без очікування, і блокування не було успішно встановлене, у необов'язковий параметр-змінну $wouldblock буде записано значення істина true.

У разі помилки функція, як завжди, повертає false, а у разі успішного завершення – true.

Повернемося до нашого прикладу з процесами-письменниками. Кожен такий процес пристрасно бажає, щоб у певний момент (точніше, коли вже майже готовий почати писати) він був єдиним, кому дозволено запис у файл. Він хоче стати винятковим.

Звідси й назва блокування, яку процес має встановити. Викликавши функцію flock($f,LOCK_EX), він може бути абсолютно впевнений, що всі інші процеси не почнуть без дозволу писати у файл, доки він не виконає всі свої дії та не викличе flock($f, LOCK_UN) або не закриє файл.

Справа в тому, що якщо в даний момент наш процес не єдиний претендент на запис, операційна система простоне випуститьйого з "нутрощів" функції flock(), тобто не допустить його продовження, поки процес- письменник не стане єдиним. Момент, коли процес, що використовує виняткове блокування, стає активним, знаменний ще й тим, що всі інші процеси-письменники очікують (все в тій же функції flock()), коли він нарешті закінчить свою роботу зфайлом. Як тільки це станеться, операційна система вибере наступний винятковий процес і т.д.

Розглянемо, як у загальному випадку має бути влаштований процес-письменник, який бажає встановити собі виняткове блокування:

Модель процесу з винятковим блокуванням

Зауважте, що при відкритті файлу ми використовували не деструктивний режим w (який видаляє файл, якщо він існував), а більш "м'який" - a+. Це недарма. Поміркуйте самі: видалення файлу ідеологічно є зміна його вмісту. Але ми не повинні це робити до отримання виняткового блокування (згадайте приклад зі світлофором)! Тому, якщо вам потрібно обов'язково щоразу стирати вміст файлу, у жодному разі не використовуйте режим відкриття w — застосовуйте a+ та функцію ftruncate(), описану вище. Наприклад:

$f=fopen($f,"a+") or die("Не можу відкрити файл на запис!");

flock ($ f, LOCK_EX); // Чекаємо, поки ми не станемо єдиними

ftruncate($f,0); // Очищаємо весь вміст файлу

Навіщо ми використовуємо fflush(), перш ніж розблокувати файл? Все дуже просто: відключення блокування не веде до скидання внутрішнього файлового буфера, тобто деякі зміни можуть бути "скинуті" у файл вже після того, як блокування буде знято. Ми, зрозуміло, цього не хочемо, і змушуємо PHP примусово записати всі зміни на диск.

Ми вирішили рівно половину нашого завдання. Справді, тепер дані з кількох процесів-письменників не перемішуватимуться, але як бути з читачами? А раптом процес-читач захоче прочитати саме з того місця, куди пише процес-письменник? І тут він, очевидно, отримає " половинчасті " дані. Тобто дані неправильні. Як же бути?

Другий (і найкращий) спосіб має на увазівикористання блокування, що розділяється. Процес, який встановлює цей вид блокування, буде припинено лише в одному випадку: коли активний інший процес, який встановив виняткове блокування. Процеси-читачі будуть "поставлені в чергу" лише тоді, коли активізується процес-письменник. І це вірно. Поміркуйте самі: навіщо запалювати червоне світло на перехресті, якщо поперечного руху свідомо немає?

Тепер давайте подивимося на блокування читачів, що розділяється, з точки зору процесу-письменника. Що він повинен робити, якщо хтось читає з файлу, в який він збирається записувати? Очевидно, він має дочекатися, доки читач не закінчить роботу. Іншими словами, виклик flock($f,LOCK_EX) повинен почекати, поки активна хоча б одне блокування, що розділяється. Це і відбувається насправді.

Модель процесу з блокуванням, що розділяється

У цьому прикладі використовуватиметься форма замовлення, змінена порівняно з тією, що ми розглядали у попередній роботі.

Новий варіант форми представлено на рис.

блокування

Кожен із замовлень записується в один файл.

Скрипт processorder.php для обробки форми наведено нижче:

Автозапчастини від Боба - Результати замовлення

Результати замовлення

echo $tireqty.' автопокришок ';

echo $oilqty.' пляшок з олією ';

echo $sparkqty.' свічок запалювання ';

$ total = $ tireqty * TIREPRICE + $ oilqty * OILPRICE + $ sparkqty * SPARKPRICE;

Разом на замовлення: '.$ total.'

Адреса доставки: '.$address.' ';

$outputstring = $date."\t".$tireqty." автопокришок \t".$oilqty." масла\t"

// Відкрити файл для додавання

$fp = fopen("orders.txt", 'a');

Сьогоднімомент вашого запиту не може бути оброблений. '