Swoole协程和Go协程的区别非常详细,棒棒哒。进程、线程、协程的概念流程是什么?进程是应用程序的启动实例。例如:打开一个软件就是启动一个进程。一个进程拥有代码和打开的文件资源、数据资源以及独立的内存空间。什么是线程?线程属于进程,是程序的执行者。一个进程至少包含一个主线程,也可以有多个子线程。线程有两种调度策略,一种是:分时调度,一种是:抢占式调度。什么是协程?协程是轻量级线程。协程的创建、切换、挂起、销毁都是内存操作,消耗很低。协程属于线程,协程是在线程中执行的。协程的调度是用户手动切换的,所以也叫用户空间线程。协程的调度策略是:协同调度。Swoole协程Swoole的协程客户端必须在协程上下文中使用。//第一种情况:Request回调本身就是一个协程环境$server->on('Request',function($request,$response){//创建一个Mysql协程客户端$mysql=newSwoole\Coroutine\MySQL();$mysql->connect([]);$mysql->query();});//第二种情况:WorkerStart回调不是协程环境$server->on('WorkerStart',function(){//需要声明协程环境才能使用协程客户端go(function(){//创建Mysql协程客户端$mysql=newSwoole\Coroutine\MySQL();$mysql->connect([]);$mysql->query();});});Swoole的协程是基于单线程的,无法利用多核CPU,同时只能调度一个。//启动4个协程$n=4;for($i=0;$i<$n;$i++){go(function()use($i){//模拟IOwaitCo::sleep(1);echomicrotime(true).":hello$i".PHP_EOL;});};echo"hellomain\n";//每次结果都一样$phptest.phphellomain1558749158.0913:hello01558749158.0915:hello31558749158.0915:hello21558749158.0915:hello1Swoole协程使用示例及详解//创建Http服务$server=newSwoole\Http\Server('127.0.0.1',OOLE/BASE,SW);当调用onRequest事件回调函数时,底层会调用C函数coro_create创建协程,//同时保存该时间点的CPU寄存器状态和ZendVM堆栈信息。$server->on('Request',function($request,$response){//创建一个Mysql协程客户端$mysql=newSwoole\Coroutine\MySQL();//在调用mysql->connect时发生,用于IO操作,底层会调用C函数coro_save保存当前协程的状态,//包括ZendVM上下文和协程描述的信息,并调用coro_yield放弃程序控制,当前请求会被挂起。//当协程放弃控制后,会继续进入EventLoop处理其他事件,Swoole会继续处理来自其他客户端的Requests。$res=$mysql->connect(['host'=>'127.0.0.1','user'=>'root','password'=>'root','database'=>'test']);//IO事件完成后,MySQL连接成功或失败,底层调用C函数coro_resume恢复对应协程,恢复ZendVM上下文,继续向下执行PHP代码。if($res==false){$response->end("MySQL连接失败");返回;}//mysql->query的执行过程和mysql->connect的执行过程一样,也会进行一次协程切换调度$ret=$mysql->query('showtables',2);//所有操作完成后,调用end方法返回结果并销毁这个协程。$response->end('swooleresponseisok,result='.var_export($ret,true));});//启动服务$server->start();Go的coroutinegoroutine是轻量级的Thread,Go语言从语言层面支持原生协程。与线程相比,Go协程的开销非常小。一个Go协程的栈开销只有2KB,可以根据程序的需要增减,而线程必须指定栈的大小,栈的大小是固定的。Goroutines是通过GPM调度模型实现的。M:表示内核级线程,一个M就是一个线程,goroutine运行在M之上。G:表示一个goroutine,它有自己的栈。P:全称是Processor,处理器。它主要用来执行goroutine,它还维护着一个goroutine队列。Go从运行时、系统调用等多个方面对goroutine的调度进行了封装和处理。当遇到长时间执行或系统调用时,它会主动转移当前协程的CPU,让其他协程调度执行。Go语言的native层支持协程层,不需要声明协程环境。packagemainimport"fmt"funcmain(){//可以直接通过Go关键字启动协程。gofunc(){fmt.Println("HelloGo!")}()}Go协程基于多线程,可以利用多核CPU。可能有多个协程同时执行。packagemainimport("fmt""time")funcmain(){//设置这个参数模拟单线程,和Swoole的协程比较//如果这个参数设置为1,每次输出都是一样的。//runtime.GOMAXPROCS(1)//启动4个协程variint64fori=0;我<4;i++{gofunc(iint64){//模拟IO等待time.Sleep(1*time.Second)fmt.Printf("hello%d\n",i)}(i)}fmt.Println("hellomain")//等待其他协程完成,如果没有,//main执行完就会退出,之后其他协程会陆续退出。time.Sleep(10*time.Second)}//第一次输出的结果$goruntest.gohellomainhello2hello1hello0hello3//第二次输出的结果$goruntest.gohellomainhello2hello0hello3hello1//以此类推,每次输出结果都不一样_"github.com/go-sql-driver/mysql"funcmain(){dsn:=fmt.Sprintf("%v:%v@(%v:%v)/%v?charset=utf8&parseTime=True&loc=Local","root","root","127.0.0.1","3306","fastadmin",)db,错误:=gorm.Open("mysql",dsn)iferr!=nil{fmt.Printf("mysql连接失败,错误:(%v)",err.Error())return}db.DB().SetMaxIdleConns(10)//设置连接池db.DB().SetMaxOpenConns(100)//设置数据库最大连接数db.DB().SetConnMaxLifetime(time.Second*7)http.HandleFunc("/test",func(writerhttp.ResponseWriter,request*http.Request){//httpRequest在协程中处理//在Go源代码src/net/http/server.go:2851line`goc.serve(ctx)`为每个请求启动一个协程varnamestringrow:=db.Table("fa_auth_rule").Where("id=?",1).Select("name").Row()err=row.Scan(&name)iferr!=nil{fmt.Printf("error:%v",err)return}fmt.Printf("name:%v\n",name)})http.ListenAndServe("0.0.0.0:8001",nil)}案例分析背景:在我们的积分策略服务系统中,使用了mongodb存储,但是swoole没有提供mongodb协程客户端。场景中,连接和操作Mongodb时会出现同步阻塞,无法发生协程切换,导致整个流程阻塞。在这段时间里,进程将无法再处理新的请求,大大降低了系统的并发度。使用同步的mongodb客户端$server->on('Request',function($request,$response){//swoole没有提供协程客户端,只能使用同步客户端//这种情况下,流程阻塞,无法切换协程$m=newMongoClient();//连接到mongodb$db=$m->test;//选择一个数据库$collection=$db->runoob;//选择集合//更新文档$collection->update(array("title"=>"MongoDB"),array('$set'=>array("title"=>"Swoole")));$cursor=$collection->find();foreach($cursoras$document){echo$document["title"]."\n";}}}通过Server->taskCo$server->on('Task',function(swoole_server$serv,$task_id,$worker_id,$data){$m=newMongoClient();//连接到mongodb$db=$m->test;//选择一个数据库$collection=$db->runoob;//选择集合//更新文档$collection->update(array("title"=>"MongoDB"),array('$set'=>array("title"=>"Swoole")));$cursor=$collection->find();foreach($cursoras$document){$data=$documentnt[“标题”];}return$data;});$server->on('Request',function($request,$response)use($server){//通过$server->taskCo()put对mongodb的操作,postittotheasynchronoustask//post到异步任务后,会发生协程切换,可以继续处理其他请求,提供并发能力$tasks[]="helloworld";$result=$server->taskCo($tasks,0.5);$response->end('测试结束,结果:'.var_export($result,true));});以上两种使用方法是Swoole中常用的方法。那么在Go中我们如何处理这个同步问题呢?其实在Go语言中大可不必担心这个问题。正如我们之前所说,Go在语言层面已经支持协程。只要有IO操作发生,网络请求就会切换协程。这也是Go语言天生就支持高并发的原因。packagemainimport("fmt""gopkg.in/mgo.v2""net/http")funcmain(){http.HandleFunc("/test",func(writerhttp.ResponseWriter,request*http.Request){session,err:=mgo.Dial("127.0.0.1:27017")如果err!=nil{fmt.Printf("Error:%v\n",err)return}session.SetMode(mgo.Monotonic,true)c:=session.DB("test").C("runoob")fmt.Printf("Connect%v\n",c)})http.ListenAndServe("0.0.0.0:8001",nil)}==并行性:同一时间,同一个CPU只能执行同一个任务。要同时执行多个任务,需要多个CPU。====并发:CPU切换时间任务非常快,你会感觉有很多任务同时执行。==CoroutineCPU密集型场景调度上面我们说的是基于IO密集型场景调度。那么如果是CPU密集型场景,应该怎么处理呢?在Swoolev4.3.2中,已经支持协程CPU密集型场景的调度。为支持CPU密集型调度,需要在编译时加入编译选项--enable-scheduler-tick,启用tick调度器。其次,我们需要手动声明declare(tick=N)语法函数来实现协程调度。$max_msec,]);$s=microtime(1);echo"start\n";$flag=1;go(function()use(&$flag,$max_msec){echo"coro1开始循环$max_msecmsec\n";$i=0;while($flag){$i++;}echo"coro1canexit\n";});$t=microtime(1);$u=$t-$s;echo"sheduleusetime".round($u*1000,5)."ms\n";go(function()use(&$flag){echo"coro2setflag=false\n";$flag=false;});echo"end\n";//输出结果startcoro1starttoloopfor10msecscheduleusetime10.2849mscoro2setflag=falseendcoro1canexitGo在CPU密集型操作期间,协程可能无法抢占CPU并永远挂起。这时需要显示调用代码runtime.Gosched()挂起当前协程,让出CPU给其他协程。packagemainimport("fmt""time")funcmain(){//如果设置了单线程,第一个协程不能放弃时间片//第二个协程一直拿不到时间片,阻塞等待.//runtime.GOMAXPROCS(1)flag:=truegofunc(){fmt.Printf("coroutineonestart\n")i:=0forflag{i++//如果加上这行代码,协程可以letTimeslice//这是一个特例,因为fmt.Printf是一个内联函数//fmt.Printf("i:%d\n",i)}fmt.Printf("coroutineoneexit\n")}()gofunc(){fmt.Printf("协程二开始\n")flag=falsefmt.Printf("协程二退出\n")}()time.Sleep(5*time.Second)fmt.Printf("end\n")}//输出结果协程一启动协程二启动协程二退出协程一退出结束注:==time.sleep()模拟IO操作,对于i++模拟CPU密集型操作。==摘要协程是开销很小的轻量级线程。Swoole的协程客户端需要在协程上下文中使用。在Swoolev4.3.2之后,已经支持协程CPU密集型场景调度。Go语言级别已经完全支持协程。更多swoole知识分享如果喜欢我的文章,想和一群资深开发者交流学习,获取更多学习资料。欢迎加入我的学习交流群677079770一起学习成长
