使用锁处理并发
当程序并发运行时,修改共享资源的某些代码部分不应同时被多个进程访问。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 在可用时提供 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 许可协议获得许可。