在看swoole的时候,看到php 定义协程的方式是
go(function () {
echo "hello wangbaojin \n";
});
此时吓我一大跳,因为之前写过golang,go里定义协程的方式是
go func
难道是php 这里又是抄袭了 go???????抱着好奇的心情展开了调查
########################################################
各种语言的协程支持情况
C++
可以通过 Boost.Coroutine 库实现协程。
Java
不支持协程
Python 3.5
加入了 async def 对协程的支持,但是Python的 协程是在方法定义时就确定了,被定义协程的方法不能当做普通方法来使用,而Go语言
swoole介绍
Swoole 使用纯 C 语言编写,提供了 PHP 语言的异步多线程服务器,异步 TCP/UDP 网络客户端,异步 MySQL,异步 Redis,数据库连接池,AsyncTask,消息队列,毫秒定时器,异步文件读写,异步DNS查询。 Swoole内置了Http/WebSocket服务器端/客户端、Http2.0服务器端。
除了异步 IO 的支持之外,Swoole 为 PHP 多进程的模式设计了多个并发数据结构和IPC通信机制,可以大大简化多进程并发编程的工作。其中包括了并发原子计数器,并发 HashTable,Channel,Lock,进程间通信IPC等丰富的功能特性。
Swoole2.0 支持了类似 Go 语言的协程,可以使用完全同步的代码实现异步程序。PHP 代码无需额外增加任何关键词,底层自动进行协程调度,实现异步。
swoole协程与go的协程对比
区别 | swoole协程 | go 协程 |
---|---|---|
底层原理 | 单进程 | 多线程 |
CSP理论 | 单进程 简单 / 不用加锁 / 性能也高 | MPG模型 |
MPG模型
CSP理论
SP模型是上个世纪七十年代提出的,用于描述两个独立的并发实体通过共享的通讯 channel(管道)进行通信的并发模型。 CSP中channel是第一类对象,它不关注发送消息的实体,而关注与发送消息时使用的channel。
Golang CSP
Golang 就是借用CSP模型的一些概念为之实现并发进行理论支持,其实从实际上出发,go语言并没有,完全实现了CSP模型的所有理论,仅仅是借用了 process和channel这两个概念。process是在go语言上的表现就是 goroutine 是实际并发执行的实体,每个实体之间是通过channel通讯来实现数据共享。
Channel
Golang中使用 CSP中 channel 这个概念。channel 是被单独创建并且可以在进程之间传递,它的通信模式类似于 boss-worker 模式的,一个实体通过将消息发送到channel 中,然后又监听这个 channel 的实体处理,两个实体之间是匿名的,这个就实现实体中间的解耦,其中 channel 是同步的一个消息被发送到 channel 中,最终是一定要被另外的实体消费掉的,在实现原理上其实是一个阻塞的消息队列。
Goroutine
Goroutine 是实际并发执行的实体,它底层是使用协程(coroutine)实现并发,coroutine是一种运行在用户态的用户线程,类似于 greenthread,go底层选择使用coroutine的出发点是因为,它具有以下特点:
用户空间 避免了内核态和用户态的切换导致的成本
可以由语言和框架层进行调度
更小的栈空间允许创建大量的实例
可以看到第二条 用户空间线程的调度不是由操作系统来完成的,像在java 1.3中使用的greenthread的是由JVM统一调度的(后java已经改为内核线程),还有在ruby中的fiber(半协程) 是需要在重新中自己进行调度的,而goroutine是在golang层面提供了调度器,并且对网络IO库进行了封装,屏蔽了复杂的细节,对外提供统一的语法关键字支持,简化了并发程序编写的成本。
附 实现高并发的几种方式
多线程
提起并发编程,最常见的就是多线程编程。线程是操作系统能够进行调度的最小单位,共享同一进程的数据和资源,有内核线程和用户线程之分,由操作系统或用户进程调度。多线程的程序可以利用多核CPU,并行地处理多个任务。随着并发量增大,线程数增加,多线程的并发模型面临一些问题。
内存占用
64位JVM线程默认栈空间是1M,启动1024个线程理论上消耗1G的栈空间。由于线程需要内存较多,为避免内存耗尽,应用程序不应该大量创建线程。
线程调度
操作系统对线程进行调度也需要成本。线程挂起前会保存线程上下文到栈空间,再切换到可执行线程。线程数很多时,线程上下文切换会导致CPU开销变大。
使用线程池技术可以对多线程并发进行优化,线程池实现线程复用,避免频繁创建新线程和线程切换,控制了线程数量,减少了内存的消耗。但是在竞争共享数据的时候,需要用加锁来保护共享数据,这样也降低了程序的并发效率。
异步回调
为了充分利用CPU,不让线程空等待,在线程阻塞的时候,注册一个回调方法,让当前线程不再阻塞,去处理新的请求。等结果准备好,调度器把结果传给回调方法,在回调方法中继续处理结果。然而回调方法并不在发起请求的线程里执行。
异步回调的缺点是所谓的callback hell。原本顺序同步的执行逻辑拆分到回调方法中,而且回调方法中可能再嵌套回调方法。这种写程序的方式还是让很多程序员不太习惯。异步回调的典型实现是NodeJS,目前也有一些第三方模块将异步代码同步化。
协程(纤程)
协程(纤程)也是一种异步方案。在代码IO阻塞时,当前协程让出CPU执行权,让其它协程执行。待IO操作完毕,阻塞的协程继续执行。虽然代码是异步执行,但写代码看起来像是同步的。协程是用户态的轻量级线程,现在的机器可以启动百万数量的协程。支持协程的编程语言实现了协程的调度器,提供了channel机制进行协程间通信(CSP模型中消息传递的实现)。基于CSP模型的协程方案,实现了无共享内存无锁的并发,可以匹配异步回调的性能。
有疑问加站长微信联系(非本文作者)