Форки и блокировки в PHP

Зачастую нужно изолировать процессы друг от друга, обеспечив их выполнение либо только одним пользователем на каждый процесс, либо обеспечить на один процесс лишь одно выполнение, либо обеспечить максимальное число потоков выполнения, либо обеспечить форк запускающий сам себя. Надежно, гарантированно обеспечить изоляцию — чтобы иной процесс не влез.

Из азов linux следует — всё, абсолютно всё в системе является файлами и все операции происходят с файлами. Это базис. Остальное — атомарность операций, блокировки, транзакции, обеспечивают уже программы — надстройки.

Напишем простой скриптик, который будет бесконечно запускать сам себя в 1 потоке. Всё остальное, о чем сказано выше, легко можно решить при помощи логики и надстроек, основное здесь — это класс надежно обеспечивающий блокировку. Для идентификатора блокировки используется имя файла, но при этом следующий процесс с тем же самым идентификатором не сможет получить блокировку, если она не снята запущенным процессом с тем же идентификатором. Т.е. это могут быть id пользователей, это могут быть номера потоков(для реализации некой многопоточности) и так далее. Очень надежная штука, проверено.

<?php

if (!Locker::lock(basename(__FILE__, '.php'))) {
    exit;
}

/**
 * здесь некий php код
 * он будет всегда выполняться лишь в одном потоке
 * далее скрипт будет запускать сам себя
 * достаточно лишь один раз запустить код
 * и он гарантированно будет выполняться лишь в 1 потоке
 * basename(__FILE__, '.php') - идентификатор
 */

Locker::unlock(basename(__FILE__, '.php'));
exec("(php -f " . __FILE__ . " &) > /dev/null");

class Locker
{
    const LOCK_PATH = __DIR__;
    const LOCK_PREFIX = 'lock';
    const CLEAN_TIME = 3600;

    private static $path;
    private static $lock = false;

    public static function lock($id, $max_time = self::CLEAN_TIME)
    {
        self::setPath($id);
        if (file_exists(self::$path) && self::cleaner()) {
            return false;
        }
        $file = new SplFileObject(self::$path, 'a+b');
        $file->flock(LOCK_EX);
        $file->rewind();
        $time = $file->fgets();
        $file->ftruncate(0);
        $file->fwrite(time() + $max_time);
        if (!$time) {
            touch(self::$path, time() + $max_time);
            $file->flock(LOCK_UN);
            self::$lock = true;
            return true; // блокировка получена
        }
        $file->flock(LOCK_UN);
        return false; // блокировка не получена
    }

    private static function setPath($id)
    {
        try {
            file_exists(self::LOCK_PATH) or mkdir(self::LOCK_PATH, 0755);
            if (!file_exists(self::LOCK_PATH)) {
                throw new Error('Path does not create');
            }
        } catch (\Throwable $th) {
            exit($th);
        }
        self::$path = self::LOCK_PATH . '/' . self::LOCK_PREFIX . '-' . $id . '.lock';
        file_exists(self::$path) && clearstatcache(true, self::$path);
    }

    public static function unlock($id, $forse = false)
    {
        self::setPath($id);
        if (file_exists(self::$path) && (self::$lock || $forse)) {
            unlink(self::$path);
            return true;
        }
        return false;
    }

    private static function cleaner()
    {
        foreach (glob(self::LOCK_PATH . '/' . self::LOCK_PREFIX . '-*.lock') as $path) {
            if (filemtime($path) < time() && intval(file_get_contents($path)) < time()) {
                unlink($path);
            }
        }
        return file_exists(self::$path);
    }
}

 

Оставить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *