跳到内容

使用锁处理并发

编辑此页

当程序并发运行时,修改共享资源的某些代码部分不应同时被多个进程访问。Symfony 的 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();

安装

在使用 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://127.0.0.101,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 自动装配锁工厂

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);

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

        $lock->release();

        // ...
    }
}

警告

在同一进程内多次调用 acquire 时,同一个 LockInterface 实例不会阻塞。当多个服务使用同一个锁时,请注入 LockFactory 以便为每个服务创建单独的锁实例。

锁定动态资源

有时应用程序能够将资源分割成小块,以便锁定进程的一小部分,而让其他进程通过。之前的示例展示了如何为所有人锁定 $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
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);

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

        $lock->release();

        // ...
    }
}

命名锁

如果应用程序需要彼此并行的不同类型的 Store,Symfony 提供了 命名锁

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

为每个命名锁创建一个自动装配别名,其名称使用其名称的驼峰式版本,并以 LockFactory 为后缀。

例如,可以通过命名参数 $invoiceLockFactory 并使用 LockFactory 进行类型提示来注入 invoice

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
    {
        // ...
    }
}
包括代码示例在内的本作品,根据 Creative Commons BY-SA 3.0 许可协议获得许可。
目录
    版本