Багатопотокова робота з блокуваннями

Дата оновлення перекладу 2024-06-08

Багатопотокова робота з блокуваннями

Коли виконання програми розпаралелене, частина коду, що змінює загальні ресурси, не повинна бути доступною одночасно декільком процесам. Компонент Symfony Lock надає механізм блокування, щоб переконатися, що лише один процес запускає критичну ділянку коду у будь-який момент часу для запобігання стану перегонів.

Приклад, що демонструє типове використання блокування:

1
2
3
4
5
6
7
8
9
$lock = $lockFactory->createLock('pdf-invoice-generation');
if (!$lock->acquire()) {
    return;
}

// критична ділянка коду
$service->method();

$lock->release();

Установка

У додатках з Symfony Flex , виконайте цю команду для установки компонента Lock:

1
$ composer require symfony/lock

Конфігурація

За замовчуванням, Symfony надає Semaphore , коли можливо, або Flock в інших випадких. Ви можете налаштувати цю поведінку, використовуючи ключ lock так:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
# config/packages/lock.yaml
framework:
    lock: ~
    lock: 'flock'
    lock: 'flock:///path/to/file'
    lock: 'semaphore'
    lock: 'memcached://m1.docker'
    lock: ['memcached://m1.docker', 'memcached://m2.docker']
    lock: 'redis://r1.docker'
    lock: ['redis://r1.docker', 'redis://r2.docker']
    lock: 'rediss://r1.docker?ssl[verify_peer]=1&ssl[cafile]=...'
    lock: 'zookeeper://z1.docker'
    lock: 'zookeeper://z1.docker,z2.docker'
    lock: 'zookeeper://localhost01,localhost02:2181'
    lock: 'sqlite:///%kernel.project_dir%/var/lock.db'
    lock: 'mysql:host=127.0.0.1;dbname=app'
    lock: 'pgsql:host=127.0.0.1;dbname=app'
    lock: 'pgsql+advisory:host=127.0.0.1;dbname=app'
    lock: 'sqlsrv:server=127.0.0.1;Database=app'
    lock: 'oci:host=127.0.0.1;dbname=app'
    lock: 'mongodb://127.0.0.1/app?collection=lock'
    lock: '%env(LOCK_DSN)%'

    # named locks
    lock:
        invoice: ['semaphore', 'redis://r2.docker']
        report: 'semaphore'

Блокування джерела

Для блокування джеррела за замовчуванням, автозмонтуйте фабрику блокувань, використовуючи LockFactory (id сервісу lock.factory):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Controller/PdfController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Lock\LockFactory;

class PdfController extends AbstractController
{
    #[Route('/download/terms-of-use.pdf')]
    public function downloadPdf(LockFactory $factory, MyPdfGeneratorService $pdf): Response
    {
        $lock = $factory->createLock('pdf-creation');
        $lock->acquire(true);

        // складні обчислення
        $myPdf = $pdf->getOrCreatePdf();

        $lock->release();

        // ...
    }
}

Caution

Той же екземпляр LockInterface не буде заблокований, якщо викликати acquire декілька разів всередині того ж процесору. Коли декілька сервісів використовують одне і те ж блокування, впровадьте LockFactory, щоб створювати новий екземпляр блокувальника для кожного сервісу.

Блокування динамічного джерела

Іноді додаток може розділити джерело на невеликі частини, щоб блокувати лише малу частину процесу, і дозволяти решті оброблятися. У попередьному прикладі $pdf->getOrCreatePdf('terms-of-use') блокувалося для всіх, а тепер давайте подивимося як заблокувати $pdf->getOrCreatePdf($version) лише для процесів, котрим потрібен доступ до тієї ж $version:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// src/Controller/PdfController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Lock\LockFactory;

class PdfController extends AbstractController
{
    #[Route('/download/{version}/terms-of-use.pdf')]
    public function downloadPdf($version, LockFactory $lockFactory, MyPdfGeneratorService $pdf): Response
    {
        $lock = $lockFactory->createLock('pdf-creation-'.$version);
        $lock->acquire(true);

        // складні обчислення
        $myPdf = $pdf->getOrCreatePdf($version);

        $lock->release();

        // ...
    }
}

Іменовані блокування

Якщо додатку потрібен інший тип сховища для різних блокувань, Symfony надає іменоване блокування :

1
2
3
4
5
# config/packages/lock.yaml
framework:
    lock:
        invoice: ['semaphore', 'redis://r2.docker']
        report: 'semaphore'

Псевдонім автомонтування створюється для кожного іменованого блокування з іменем, що використовує camel case версію свого імені з суфіксом LockFactory.

Наприклад, блокування invoice може бути впроваджено шляхом іменнування аргументу $invoiceLockFactory та додавання до нього підказки LockFactory:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// src/Controller/PdfController.php
namespace App\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Lock\LockFactory;

class PdfController extends AbstractController
{
    #[Route('/download/terms-of-use.pdf')]
    public function downloadPdf(LockFactory $invoiceLockFactory, MyPdfGeneratorService $pdf): Response
    {
        // ...
    }
}