【问题标题】:PHP mutual exclusion (mutex)PHP互斥(mutex)
【发布时间】:2011-02-24 16:11:40
【问题描述】:

阅读一些关于 PHP 锁定的文章。
他们都,主要是直接到http://php.net/manual/en/function.flock.php

本页讨论的是在硬盘上打开文件!!

真的是这样吗?我的意思是,这使得锁定非常昂贵 - 这意味着每次我想锁定我都必须访问硬盘)=

可以再用一个令人愉快的消息来安慰我吗?

编辑:

由于我在这里得到了一些回复,我想问这个;
我的脚本只能由一个线程运行,还是由多个线程运行?因为如果它是一个,那么我显然不需要互斥锁。有简洁的答案吗?

我到底想要做什么

ircmaxell 提问。
故事是这样的:

我有两个 ftp 服务器。我希望能够在我的网站上显示有多少在线用户在线。
所以,我认为这些 ftp 服务器会将它们的统计信息“发布”到某个 PHP 脚本页面。我们假设这个页面的 URL 是“http://mydomain.com/update.php”。

在网站首页(“http://mydomain.com/index.php”)我会显示累计统计(在线用户)。

就是这样。

我的问题是我不确定,当一个 ftp 服务器更新他的统计数据而另一个也这样做时,信息是否会混淆。
就像多线程时一样;两个线程同时增加一些“int”变量。除非您在它们之间进行同步,否则它不会按预期发生。
那么,我会有问题吗?是的,不是的,也许吧?

可能的解决方案

苦苦思索了一整天,我在这里有个想法,希望你给出你的意见。
如前所述,这些 ftp 服务器将每 60 秒发布一次他们的统计信息。
我正在考虑使用此文件“stats.php”。
它将包含在 ftp 服务器转到的更新脚本(“update.php”)和访问者看到有多少用户在线的“index.php”页面中。
现在,当 ftp 服务器更新时,“update.php”处的脚本将使用新的累积统计信息修改“stats.php”。
首先它将读取“stats.php”中包含的统计信息,然后累积,然后重写该文件。

如果我没记错的话,PHP 会检测到文件(“stats.php”)被更改并加载新文件。对吗?

【问题讨论】:

  • PHP 是否支持线程?查看 PHP API,我看不到任何与线程相关的内容...
  • 出于某种原因,我认为 PHP 支持线程,它会同时为多个客户端执行相同的脚本。
  • 所以我有一个静态变量,它将保存一些数据,这些数据由多个来源更新并提供给多个客户端。这可能同时发生,因此我需要某种方式来同步。
  • @tloach,PHP 是一种语言。它可以通过其扩展库支持所有内容。

标签: php mutex mutual-exclusion


【解决方案1】:

我最近使用 PHP 的flock 函数创建了自己的类似互斥锁机制的简单实现。当然,下面的代码可以改进,但它适用于大多数用例。

function mutex_lock($id, $wait=10)
{
  $resource = fopen(storage_path("app/".$id.".lck"),"w");

  $lock = false;
  for($i = 0; $i < $wait && !($lock = flock($resource,LOCK_EX|LOCK_NB)); $i++)
  {
    sleep(1);
  }

  if(!$lock)
  {
    trigger_error("Not able to create a lock in $wait seconds");
  }

  return $resource;
}

function mutex_unlock($id, $resource)
{
  $result = flock($resource,LOCK_UN);
  fclose($resource);

  @unlink(storage_path("app/".$id.".lck"));

  return $result;
}

【讨论】:

    【解决方案2】:

    问题是:您将把 FTP 服务器通过 POST 推送到您的 update.php 文件的统计信息存储在哪里?如果它是本地文件,那么第二篇文章中的 ircmaxell 已经回答了您。您也可以使用互斥锁来执行此操作 - 信号量函数。另一种解决方案是使用 MySQL MyISAM 表来存储统计信息并使用类似update info_table set value = value + 1 的东西。它应该锁定表,并序列化您的请求,您不会有任何问题。

    【讨论】:

      【解决方案3】:

      嗯,大部分 PHP 运行在不同的进程空间中(很少有线程实现)。最容易的是羊群。保证可以在所有平台上运行。

      但是,如果您在支持下进行编译,则可以使用其他一些东西,例如 Semaphore 扩展。 (使用 --enable-sysvsem 编译 PHP)。然后,您可以执行类似的操作(注意,sem_acquire() 应该阻塞。但如果由于某种原因不能,它将返回 false):

      $sem = sem_get(1234, 1);
      if (sem_acquire($sem)) {
          //successful lock, go ahead
          sem_release($sem);
      } else {
          //Something went wrong...
      }
      

      您拥有的其他选项是 MySQL user level locks GET_LOCK('name', 'timeout'),或者使用 APC 或 XCache 之类的东西创建您自己的(注意,这不是真正的锁,因为竞争条件可以在其他人的地方创建在您的检查和接受锁定之间获得锁定)。

      编辑:回答您编辑的问题:

      这完全取决于您的服务器配置。 PHP 可以多线程运行(每个请求由不同的线程处理),也可以多进程运行(每个请求由不同的进程处理)。这完全取决于您的服务器配置...

      PHP 很少会串行处理所有请求,只有一个进程(和一个线程)处理所有请求。如果您使用的是 CGI,那么默认情况下它是多进程的。如果您使用的是 FastCGI,则可能是多进程和多线程。如果您将 mod_php 与 Apache 一起使用,则取决于工作人员类型:

      1. mpm_worker 将同时是多进程和多线程,进程数由 ServerLimit 变量指定。
      2. prefork 将是多进程的
      3. perchild 也将是多进程

      编辑:回答您编辑的第二个问题:

      这很容易。将其存储在文件中:

      function readStatus() {
          $f = fopen('/path/to/myfile', 'r');
          if (!$f) return false;
          if (flock($f, LOCK_SH)) {
              $ret = fread($f, 8192);
              flock($f, LOCK_UN);
              fclose($f);
              return $ret;
          }
          fclose($f);
          return false;
      }
      
      function updateStatus($new) {
          $f = fopen('/path/to/myfile', 'w');
          if (!$f) return false;
          if (flock($f, LOCK_EX)) {
              ftruncate($f, 0);
              fwrite($f, $new);
              flock($f, LOCK_UN);
              fclose($f);
              return true;
          }
          fclose($f);
          return false;
      }
      
      function incrementStatus() {
          $f = fopen('/path/to/myfile', 'rw');
          if (!$f) return false;
          if (flock($f, LOCK_EX)) {
              $current = fread($f, 8192);
              $current++;
              ftruncate($f, 0);
              fwrite($f, $current);
              flock($f, LOCK_UN);
              fclose($f);
              return true;
          }
          fclose($f);
          return false;
      }
      

      【讨论】:

      • 不同的进程空间,但我可以有一个静态变量(又名单例)在所有客户端之间共享 - 怎么会?
      • 这个 MySQL 用户级锁很好,但它需要 TCP 连接和 MySQL 机器/实例。最好使用基于文件的锁.. 不过谢谢!
      • 您不能在多个 php 实例之间共享一个变量。好吧,如果您将其序列化并将其存储在全局存储中(如 APC、memcached、MySQL 等),则可以。但是每个“客户”都会创建自己的实例。你到底想做什么?
      • !!!!!!警告:当您拥有一个高负载的网站时,基于文件的锁是不可靠的。您不能对文件执行互斥锁。
      • @Slawek,怎么会这样?这是我第一次听到这个,我找不到任何东西来支持你的说法。 (它们在 NFS 挂载上不可靠,但那是另一回事。它们是自愿的,因此不能保证没有人在使用该文件,但这与这里无关。)
      【解决方案4】:

      PHP 不支持多线程,每个请求(因此每个 PHP 脚本)将仅在一个线程(甚至进程,取决于您运行 PHP 的方式)中执行。

      【讨论】:

      • PHP 支持多线程,使用异步 HTTP 即可轻松实现。
      • 等等,异步 HTTP 是客户端,这并不意味着服务器端是多线程运行的(每个异步请求都作为单线程 PHP 脚本执行)。
      • pcntl_fork 会让你做真正的多线程,我想知道为什么没人提到它。
      • 没有 pcntl_fork 可以让您进行多处理,而不是多线程。我有一些经验:) github.com/shaneharter/PHP-Daemon
      • 虽然PHP不支持运行多线程,但它运行在多线程或多进程的环境中。
      【解决方案5】:

      是的,因为 PHP 是由 Apache 运行的,Apache 可以按照它认为最好的方式组织执行线程(请参阅各种工作模型)。因此,如果您想一次访问一个资源,您可以锁定文件(例如,如果您正在处理 cron 作业,这很好),或者您依赖数据库事务机制、ACID 特性和数据库资源锁定,如果您正在处理数据。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-06-04
        • 2013-01-15
        • 2013-02-02
        • 2010-12-26
        • 1970-01-01
        • 2011-01-06
        • 2018-05-23
        • 1970-01-01
        相关资源
        最近更新 更多