Dealing with Concurrency with Locks

When a program runs concurrently, some part of code which modify shared resources should not be accessed by multiple processes at the same time. Symfony's Lock component provides a locking mechanism to ensure that only one process is running the critical section of code at any point of time to prevent race conditions from happening.

プログラムが同時に実行される場合、共有リソースを変更するコードの一部は、同時に複数のプロセスからアクセスされるべきではありません。競合状態が発生しないようにします。

The following example shows a typical usage of the lock:

次の例は、ロックの一般的な使用法を示しています。
1
2
3
4
5
6
7
8
9
$lock = $lockFactory->createLock('pdf-creation');
if (!$lock->acquire()) {
    return;
}

// critical section of code
$service->method();

$lock->release();

Installing

In applications using Symfony Flex, run this command to install the Lock component:

Symfony Flex を使用するアプリケーションでは、次のコマンドを実行して Lock コンポーネントをインストールします。
1
$ composer require symfony/lock

Configuring

By default, Symfony provides a Semaphore when available, or a Flock otherwise. You can configure this behavior by using the lock key like:

デフォルトでは、Symfony は利用可能な場合は Semaphore を提供し、そうでない場合は Flock を提供します。次のようなロック キーを使用して、この動作を構成できます。
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
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
# 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: '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'

6.1

6.1

The CSV support (e.g. zookeeper://localhost01,localhost02:2181) in ZookeeperStore DSN was introduced in Symfony 6.1.

ZookeeperStore DSN の CSV サポート (例: Zookeeper://localhost01,localhost02:2181) は、Symfony 6.1 で導入されました。

Locking a Resource

To lock the default resource, autowire the lock factory using LockFactory:

デフォルトのリソースをロックするには、LockFactory を使用してロック ファクトリを自動配線します。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/Controller/PdfController.php
namespace App\Controller;

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

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

        // heavy computation
        $myPdf = $pdf->getOrCreatePdf();

        $lock->release();

        // ...
    }
}

Caution

注意

The same instance of LockInterface won't block when calling acquire multiple times inside the same process. When several services use the same lock, inject the LockFactory instead to create a separate lock instance for each service.

同じプロセス内で acquire を複数回呼び出した場合、LockInterface の同じインスタンスはブロックされません。複数のサービスが同じロックを使用する場合は、代わりに LockFactory を注入して、サービスごとに個別のロック インスタンスを作成します。

Locking a Dynamic Resource

Sometimes the application is able to cut the resource into small pieces in order to lock a small subset of processes and let others through. The previous example showed how to lock the $pdf->getOrCreatePdf() call for everybody, now let's see how to lock a $pdf->getOrCreatePdf($version) call only for processes asking for the same $version:

アプリケーションは、プロセスの小さなサブセットをロックして他のプロセスを通過させるために、リソースを小さな断片に分割できる場合があります。前の例では、全員に対して $pdf->getOrCreatePdf() 呼び出しをロックする方法を示しました。次に、同じ $version を要求するプロセスに対してのみ $pdf->getOrCreatePdf($version) 呼び出しをロックする方法を見てみましょう。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// src/Controller/PdfController.php
namespace App\Controller;

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

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

        // heavy computation
        $myPdf = $pdf->getOrCreatePdf($version);

        $lock->release();

        // ...
    }
}

Naming Locks

If the application needs different kind of Stores alongside each other, Symfony provides named lock:

アプリケーションが異なる種類のストアを並べて必要とする場合、Symfony は名前付きロックを提供します:
  • YAML
    YAML
  • XML
    XML
  • PHP
    PHP
1
2
3
4
5
# config/packages/lock.yaml
framework:
    lock:
        invoice: ['semaphore', 'redis://r2.docker']
        report: 'semaphore'

An autowiring alias is created for each named lock with a name using the camel case version of its name suffixed by LockFactory.

キャメルケース バージョンの名前に LockFactory という接尾辞を付けた名前を持つ名前付きロックごとに、オートワイヤー エイリアスが作成されます。

For instance, the invoice lock can be injected by naming the argument $invoiceLockFactory and type-hinting it with LockFactory:

たとえば、請求書のロックは、引数 $invoiceLockFactory に名前を付け、LockFactory で型ヒントを付けることで注入できます。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// src/Controller/PdfController.php
namespace App\Controller;

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

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