首先,了解下协程是什么??
协程是一种用户态的轻量级线程,协程的调度完全由用户控制。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内核切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快。

swoole创建协程

go(function(){
        echo "coroutine 111";
});
echo "main hello";
go(function(){
        echo "coroutine 222";
});

更改下体验协程调度

go(function(){
        Co::sleep(2);
        echo "coroutine 111";
});
echo "main hello";
go(function(){
        echo "coroutine 222";
});

执行过程:当执行go的时候生成一个协程,当协程遇到阻塞(Co::sleep(2)),协程让出控制,进入协程控制队列。继续往下执行。输出coroutine 222,等待之前的的协程阻塞解除,输出coroutine 111
以上只是简单的协程认识,接下来我们看下协程到底快在那里?

$start_time = time();
for($i=0;$i<500;$i++){
        $url = "http://www.baidu.com/";
        $content = file_get_contents($url);
        echo "普通{$i}已完成".PHP_EOL;
}
echo "非协程完成时间:".(time() - $start_time).PHP_EOL;

结果如图

$start_time = time();
for($i=0;$i<500;$i++){
        go(function()use($i, $start_time){
                $cli = new Swoole\Coroutine\Http\Client('http://www.baidu.com');
                $cli->setHeaders([
                        'Host' => "www.baidu.com",
                        "User-Agent" => 'Chrome/49.0.2587.3',
                        'Accept' => 'text/html,application/xhtml+xml,application/xml',
                        'Accept-Encoding' => 'gzip',
                ]);
                $cli->get('/');
                $cli->close();
                echo "协程{$i} 完成,耗时".(time()-$start_time).PHP_EOL;
        });
}

结果如图:

管道(channel)

Channel 管道:支持多生产者协程和多消费者协程。底层自动实现了协程的切换和调度。
Channel 管道是用于同一进程内协程之间交换数据的工具,可以理解为,Channel 就是一个实现了协程切换和调度的队列,亦或是数组。
生产协程:在channel已满时,会被挂起;
消费协程:在channel为空是,也会被挂起。

$chan = new Chan();
go(function()use($chan){
        for($i=0;$i<5;$i++){
                $chan->push($i);
                echo "顺序插入{$i}".PHP_EOL;
        }
});
echo "顺序执行".PHP_EOL;
go(function()use($chan){
        while(!$chan->isEmpty()){
                $res = $chan->pop();
                echo "顺序消费{$res}".PHP_EOL;
        }
});

结果可以看出生产者协程和消费者协程是交替运行的,而协程切换的时机则是在运行到 push 和 pop 的时候,首先会进入生产者协程,然后生产了一条数据,然后代码继续执行输出“顺序执行”的字符串并创建了消费者协程;由于前面已经 push 了一条数据所以此时的 $channel->isEmpty() 是非空状态,再执行 pop。

链接池

由于管道(channel)的特性(写入消费),可以通过管道实现连接池。

连接池是一个用于分配和管理连接的容器,可以避免在高并发的系统下反复地去创建和销毁连接,便于连接的复用。

class db{

        public $pool;
        public $config = [
                'maxnum'=>20,
                'mysql' =>[
                           'host'=>'127.0.0.1',
                           'port'=>3306,
                           'user'=>'root',
                           'password'=>'',
                           'database'=>'demo'
                          ]
        ];

        public function __construct(){
                $maxnum = $this->config['maxnum'];
                $this->pool = new \Swoole\Coroutine\Channel($maxnum);
                for($i=1;$i<$maxnum;$i++){
                        $mysqlConnect = $this->createConn();
                        $this->push($mysqlConnect);
                }
        }

        public function push($source){
                return $this->pool->push($source);
        }

        public function get(){
                return $this->pool->pop();
        }

        public function length(){
                return $this->pool->length();
        }

        /** 创建数据库连接 */
        public function createConn(){
                $mysql = new \Swoole\Coroutine\MySQL();
                $mysql->connect($this->config['mysql']);
                return $mysql;
                //$res = $mysql->query('select 1+1 as sum');
                //var_dump($res);
        }
}

go(function(){
        $obj = new db();
        $conn = $obj->get();
        $res = $conn->query('select 1+1 as sum');
        var_dump($res);
        //echo $obj->length();
        //$obj->createConn();
});

以上就是一个简单的链接池,通过 Channel 实现一个连接池,并轻而易举地实现了获取连接的等待功能。

作者:ysp123
链接:https://www.jianshu.com/p/b0f504551748
来源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。