diff --git a/src/Controller/FileDownloadController.php b/src/Controller/FileDownloadController.php
index d0125f8..12b4636 100644
--- a/src/Controller/FileDownloadController.php
+++ b/src/Controller/FileDownloadController.php
@@ -152,7 +152,7 @@ class FileDownloadController extends ControllerBase {
 
       if (count($headers)) {
         $image = $this->imageFactory->get($uri);
-        if (($webp = $this->webp->createWebpCopy($image->getSource())) && in_array('image/webp', $request->getAcceptableContentTypes())) {
+        if (($webp = $this->webp->getWebpCopy($image->getSource())) && in_array('image/webp', $request->getAcceptableContentTypes())) {
           return $this->webpResponse($webp, $headers, $scheme);
         }
         else {
diff --git a/src/Controller/ImageStyleDownloadController.php b/src/Controller/ImageStyleDownloadController.php
index 2816fef..af6ecf3 100644
--- a/src/Controller/ImageStyleDownloadController.php
+++ b/src/Controller/ImageStyleDownloadController.php
@@ -207,7 +207,7 @@ class ImageStyleDownloadController extends FileDownloadController {
     if ($success) {
       $image = $this->imageFactory->get($derivative_uri);
 
-      if ($webp_wanted && ($webp = $this->webp->createWebpCopy($image->getSource())) && in_array('image/webp', $request->getAcceptableContentTypes())) {
+      if ($webp_wanted && ($webp = $this->webp->getWebpCopy($image->getSource())) && in_array('image/webp', $request->getAcceptableContentTypes())) {
         return $this->webpResponse($webp, $headers, $scheme);
       }
       else {
diff --git a/src/Plugin/ImageAPIOptimizeProcessor/Webp.php b/src/Plugin/ImageAPIOptimizeProcessor/Webp.php
index 0dc2435..d980c31 100644
--- a/src/Plugin/ImageAPIOptimizeProcessor/Webp.php
+++ b/src/Plugin/ImageAPIOptimizeProcessor/Webp.php
@@ -22,7 +22,7 @@ class Webp extends ConfigurableImageAPIOptimizeProcessorBase {
   public function applyToImage($image_uri) {
     /* @var \Drupal\webp\Webp $webp */
     $webp = \Drupal::service('webp.webp');
-    $webp->createWebpCopy($image_uri, $this->configuration['quality']);
+    $webp->getWebpCopy($image_uri, $this->configuration['quality']);
   }
 
   /**
diff --git a/src/Webp.php b/src/Webp.php
index bbd6de2..eb7b60f 100644
--- a/src/Webp.php
+++ b/src/Webp.php
@@ -2,14 +2,17 @@
 
 namespace Drupal\webp;
 
+use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\File\Exception\FileException;
 use Drupal\Core\Image\ImageFactory;
 use Drupal\Core\File\FileSystemInterface;
+use Drupal\Core\Lock\LockBackendInterface;
 use Drupal\Core\Logger\LoggerChannelFactoryInterface;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Imagick;
+use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException;
 
 /**
  * Class Webp.
@@ -48,6 +51,13 @@ class Webp {
    */
   protected $fileSystem;
 
+  /**
+   * The lock backend.
+   *
+   * @var \Drupal\Core\Lock\LockBackendInterface
+   */
+  protected $lock;
+
   /**
    * Webp constructor.
    *
@@ -61,17 +71,20 @@ class Webp {
    *   Configuration factory.
    * @param Drupal\Core\File\FileSystemInterface $fileSystem
    *   The file system service.
+   * @param \Drupal\Core\Lock\LockBackendInterface $lock
+   *   Lock backend interface.
    */
-  public function __construct(ImageFactory $imageFactory, LoggerChannelFactoryInterface $loggerFactory, TranslationInterface $stringTranslation, ConfigFactoryInterface $configFactory, FileSystemInterface $fileSystem) {
+  public function __construct(ImageFactory $imageFactory, LoggerChannelFactoryInterface $loggerFactory, TranslationInterface $stringTranslation, ConfigFactoryInterface $configFactory, FileSystemInterface $fileSystem, LockBackendInterface $lock) {
     $this->imageFactory = $imageFactory;
     $this->logger = $loggerFactory->get('webp');
     $this->setStringTranslation($stringTranslation);
     $this->defaultQuality = $configFactory->get('webp.settings')->get('quality');
     $this->fileSystem = $fileSystem;
+    $this->lock = $lock;
   }
 
   /**
-   * Creates a WebP copy of a source image URI.
+   * Get a WebP copy of a source image URI.
    *
    * @param string $uri
    *   Image URI.
@@ -81,7 +94,46 @@ class Webp {
    * @return bool|string
    *   The location of the WebP image if successful, FALSE if not successful.
    */
-  public function createWebpCopy($uri, $quality = NULL) {
+  public function getWebpCopy($uri, $quality = NULL) {
+    $destination = $this->getWebpDestination($uri, '@directory@filename.webp');
+
+    // Don't start generating the webp copy if it already exists or if
+    // generation is in progress in another thread.
+    if (!file_exists($destination)) {
+      $lock_name = 'webp_create_copy:' . Crypt::hashBase64($uri);
+      $lock_acquired = $this->lock->acquire($lock_name, 3);
+      if (!$lock_acquired) {
+        // Tell client to retry again in 3 seconds. Currently no browsers are
+        // known to support Retry-After.
+        throw new ServiceUnavailableHttpException(3, $this->t('Image generation in progress. Try again shortly.'));
+      }
+    }
+
+    // Try to generate the image, unless another thread just did it while we
+   // were acquiring the lock.
+    $success = file_exists($destination) || $this->createWebpCopy($uri, $destination, $quality);
+
+    if (!empty($lock_acquired)) {
+      $this->lock->release($lock_name);
+    }
+
+    return $success ? $destination : FALSE;
+  }
+
+  /**
+   * Generate a WebP copy of a source image URI.
+   *
+   * @param string $uri
+   *   Image URI.
+   * @param string $destination
+   *   Image source destination.
+   * @param int $quality
+   *   Image quality factor (optional).
+   *
+   * @return bool|string
+   *   The location of the WebP image if successful, FALSE if not successful.
+   */
+  protected function createWebpCopy($uri, $destination, $quality = NULL) {
     $webp = FALSE;
 
     $toolkit = \Drupal::config('system.image')->get('toolkit', FALSE);
@@ -114,8 +166,6 @@ class Webp {
       // If we can generate a GD resource from the source image, generate the URI
       // of the WebP copy and try to create it.
       if ($sourceImage !== NULL) {
-        $destination = $this->getWebpDestination($uri, '@directory@filename.webp');
-
         imagesavealpha($sourceImage, TRUE);
         imagealphablending($sourceImage, TRUE);
         imagesavealpha($sourceImage, TRUE);
diff --git a/webp.services.yml b/webp.services.yml
index 4baafef..9b461cf 100644
--- a/webp.services.yml
+++ b/webp.services.yml
@@ -6,4 +6,4 @@ services:
       - { name: event_subscriber }
   webp.webp:
     class: Drupal\webp\Webp
-    arguments: ['@image.factory', '@logger.factory', '@string_translation', '@config.factory', '@file_system']
+    arguments: ['@image.factory', '@logger.factory', '@string_translation', '@config.factory', '@file_system', '@lock']
