Swoole 协程使用注意事项

近期公司的项目需要,用Swoole搭建TCP服务器,过程中使用了Coroutine\Redis,以及Coroutine\Mysql,在第一个版本中为了节省TCP及Redis的连接数,协程的理解的不够深入,多个协程使用了同一个Redis及Mysql客户端,导致不同的协程之间发生了数据错乱。
在此简要分析如下:

简单数据类型:
$number = 1;
go(function () use(&$number) {
  Co::sleep(1);
  $number++;
  echo "In First Coroutine Number is:".$number;
});

$number++;
echo "In Main ".$number."\n";

go(function () use(&$number) {
  $number++;
  echo "In Second Coroutine Number is:".$number;
});

本来想获得顺序为

In First Coroutine Number is:2
In Main 3
In Second Coroutine Number is:4

运行此代码获得如下结果:

In Main 2
In Second Coroutine Number is:3
In First Coroutine Number is:4

示例很简单,但是展示了协程的乱序可能会导致的程序错误。
在调试过程中因为使用了单一连接客户端导致过:

Uncaught Swoole\Error: Socket#8 has already been bound to another coroutine

因此在swoole中使用协程时,一般使用协程客户端连接池。
示例代码如下:


/**
 * Class RedisPool
 */
class RedisPool
{
  protected $mAvailable;
  protected $mPool;
  protected $mConfig;

  public function __construct($config)
  {
    $this->mAvailable = true;
    $this->mPool = new SplQueue();
    $this->mConfig = $config;
  }

  public function push($redisClient)
  {
    if ($this->mAvailable) {
      $this->mPool->enqueue($redisClient);
    }
  }

  public function pop()
  {
    $redisClient = null;
    if ($this->mAvailable) {
      try {
        $redisClient = $this->mPool->dequeue();
      } catch (\RuntimeException $exception) {
        //队列中为空
        $redisClient = new RedisClient($this->mConfig);
        if (empty($redisClient) || $redisClient->connect()) {
          $redisClient = null;
        }
      }
    }
    return $redisClient;
  }

  public function isAvailable()
  {
    return $this->mAvailable;
  }

  public function setAvailable($available)
  {
    $this->mAvailable = $available;
  }


  public function __destruct()
  {
    // 连接池销毁, 置不可用状态, 防止新的客户端进入常驻连接池, 导致服务器无法平滑退出
    $this->mAvailable = false;
    while (!$this->mPool->isEmpty()) {
      $this->mPool->dequeue();
    }
  }

  public function __call($name, $arguments)
  {
    // TODO: Implement __call() method.
    if (!is_callable(array($this->mPool, $name))) {
      throw new BadFunctionCallException("Function Name:" . $name . " Args:" . implode(",", $arguments));
    }
    return call_user_func_array([$this->mPool, $name], $arguments);
  }

在协程中,从连接池中获取连接客户端,保证每个协程使用不同的连接