目前主流意识是让PHP程序员转GO,认为Go的语法比较简单,甚至还没PHP的多,但这是一个错误意识,越简单越说明底层越暴露,对PHP程序员的冲击越大,即使PHP程序员勉强转了Go,也只不过是个皮毛,会个CRUD
第一步:
PHP程序员应该先把 hyperf 框架学了,知道什么是协程,知道什么是常驻内存服务,告别PHP-FPM启动方式,对HTTP协议有更深的了解,知道Nginx 和 YII/Laravel不是直接通信的,知道$_SERVER,$_POST 这些变量是有php-fpm装载的,nginx是怎么通过fast-cgi协议将http的url 以及 header body 参数传递给fast-cgi的,以及原来自己写的web框架,只是类似于一个js文件被一个进程拿过去运行后返回一个response结果,然后就结束了它的生命周期,而并没有常驻内存,这种方式竟然和java,python这类常驻内存的语言不一样?常驻的好处是单例和多开线程。
卧槽,此时你才发现PHP的单例没屌用,只是看起来好,实际没起到作用,每次都被运用了一次,还怎么叫单例?
第二步:
理解了常驻服务之后,就要抛弃PHP的单次请求就单开一个进程的运行模式,改为常驻线程的思考方式。
既然常驻内存了,那么所有的Controller类的__constructor或者init()函数只会运行一次,全部变成了线程内的真单例,只在服务第一次启动时被实例化,之后不再new,每次传过来的数据都是CPU重新设置进去,而为本次请求特意准备的,因此在__constructor搞拦截和判断的思维就要摈弃了,而要改为中间件拦截的思考方式,因为中间件每次都将发过来http重新装载到单例的Request中去,而不是重新new 一个Request,将数据装填进去,此时你该问,那多出来的Request数据怎么办,Nginx不是可以一下子支持多个请求吗?没错,他们都被作为一个队列缓存起来了,由不同的线程以及协程交替取出处理,然后再将返回数据一个个按队列发回去。
第三步:
有了常驻内存的意识之后,就要了解线程和协程的运行规则,为何线程不能解决高兵发问题,而协程可以解决,但又不能只使用协程?只使用协程,可以理解为只有一个人来工作,如果一个公务员只处理盖章这个事情,整个审批还得其他公务员继续推进,那么单个盖章的公务员的最高盖章速度是有限的,因此,一个盖章公务员的确可以为大量办证人员盖章,但是超过这个限度,剩下的办证用户就要等待,此时就要再加一个盖章人员B,来分担盖章工作,整个分布式的工作体系就形成了纯异步单纯又饱和的流水线工作就开始了。
基于上述的线程和协程协作方式,进程和线程就不需要区分那么细了,因为要将一个线程充分饱和,那就是一个线程独占一个进程,而这个进程独占一个CPU核心,这样线程就不用在CPU层面来回切分了。
此时多核CPU就开始成为了提升并发量唯一武器,因此最后的结局就是一个服务器有4核,那就开4个进程,每个进程里一个线程,这个线程里有N多个协程,协程的多少根据内存的大小决定,内存决定了请求的队列到底有多大,超过内存的数据会被放到硬盘,硬盘和内存交互的话,速度就大大降低了,这肯定不是要解决并发方案,此时,该服务器的性能被榨取到最大化,人对机器就是这么没有人性,😄
第四步:
了解了协程,你就会接触生成器,channel等一些新概念,这些新概念,仅仅从hyperf框架本身理解是很不直观的,因为你要搞服务器,搞部署,搞前端对接,启动PHP服务等环节,做了80%跟理解yeild无关的事情,那么有没有很好地理解方式呢?答案是转向你最熟悉的Javascript吧,此时,你可能卧槽,JS还有这个?
第五步:
你没看错,几乎所有的语言都有yeild语法,他们叫做生成器,这个东西,可以暂存CPU运行状态,让CPU去干别的活,等到时机成熟,会重新将暂存的状态拉起,再次执行,这样做有什么好处呢?现实生活中,我们很多时候都是等待结果的到来,例如等快递,等审核,等报备,再等待期间不影响我们看电影,打游戏,所以这是正常的现实映射。
JS中大量的Ajax请求都需要等待,这么等待他们是怎么挂起之后再重新拉起回调函数的呢?你们不觉得很有趣吗?但却从来没注意过是吧?那就去看一看《你不知道的Javascript》了解一下Promise + 生成器,到底解决了什么问题。
期间你还会发现一个很有意思的东西,那就是生成器就像一个函数,是需要被循环调用的,不然yield暂停了,再也不能被调用,那程序不是永远卡在那里不动了吗?所以迭代器就出现了。
此时什么叫做for,什么叫做while,直到此时你发现原来for 和while 可以被理解成更低阶的JUMP。
没错,JUMP就是汇编里面的指令,此时不要怕,没到这么深,这里只是让你知道连续是建立在非连续的基础之上的,你以为的一瞬间的就循环完的过程,实际上是由一个类似JUMP概念跳来跳去完成的,而这个跳来跳去不是从A到B,然后再从B到A,你可以想象成一排断桥,一只蛤蟆从像Z字形状断桥最上面的端点开始,来回跳,自上而下跳的,那么每跳到Z字一个点都去执行一段程序,执行完了再跳到下一个端点,只不过每个点逻辑类似,但是数据不一样。
迭代器就是这只蛤蟆,它的每一次next() 都意味着蛤蟆要跳完后执行一段代码,for的作用是连续的让迭代器调用next(),如果不用for,而用程序来控制,你就可以控制蛤蟆要不要跳,还有很多只蛤蟆在很多个断桥上,每当有一只青蛙跳完后,他都可能会呼叫其他青蛙继续跳,但他不能记住那么多青蛙,所以告诉程序,由程序排好队来安排接下来跳的青蛙,这个程序就叫事件循环服务,事件循环服务降低了蛤蟆之间的耦合度,保证了蛤蟆跳跃的顺序。
因此迭代器会把生成器的代码拆成了好几段,拆分的依据就是yield关键词,每按顺序调用一次next()函数,就会走到下一个yeild语句出。
这样讲,应该是真把这个复杂的概念彻底讲通了吧!😁
此时,你应该发现了一点端倪,程序在执行层面,并不像你想象的那样,是按照你看到的结构进行的,底层经过了复杂调度处理,你书写的方式,只是让你更简单的下发这样一个指令,指令具体怎么组织,会经过编译器来再次调整。
理解了这个概念,你或许就一下子理解什么是语法糖了,语法糖正式为了让你更好的下发指令,而设好的一种指示牌,就如打仗时举红旗发炮,举蓝旗冲锋一个道理,执行的时候,会把这种指令翻译成正常语言的组织形式,这种重组织过程叫做预编译。
同理,SpringBoot注解方式,正是将预编译这个概念用到了极致,你不知道SB,那你知道Hyperf,Hyperf也是注解方式,一开始PHP程序员很难理解注解是个语言吗?但点进去发现啥也没有,那又是怎么运行的呢?这正如老板批注秘书的方案,上面加了一句,请把上次团建的照片选择5张好的贴进来,这里注解的参数有几个,第一,团建的照片,第二,照的好的5张,所以注解是有参数的,但是注解是没有实现体的,那么谁来实现呢?
预编译器,Hyperf 启动的时候,会有一个预编译过程,会将你所有注解的地方,都根据编译器里的逻辑,将注解以及注解的参数,转化为具体的PHP代码,补充到该补充的地方去,然后形成一套新的可运行的代码,说到这里你是不是对注解彻底的理解,也明白到底去哪里找注解的实现代码了?
而很多人将注解和修饰器混淆,这是不应该的,修饰器就是一个函数的前置后置拦截器,可以在函数前置后置时做一些预处理。
这些预处理如果每次都侵入函数本体,造成难以维护,所以干脆抽离出来作为一个单独的函数,由编译器编译时给它加上,注意,编译器是真冗余的一个个加上了,机器就是干了自己纠结的事情,爽了程序员。
最具有特色语法糖就是async+await。
第六步:
在你彻底搞懂 JS 生成器+迭代器如何完美将异步转化为同步代码的认知下,你再回过来看hypef的协程运行机制,你会恍然大悟,我 *** 原来是这样,但是hyperf封装的足够深,你要看源码,又要被80%毫不相关的web框架设计模式以及注解(也是预编译的概念,相当于领导批改稿子,加了一句话,请把调研数据加到这里,于是秘书拿到你的批改后,重新整理这份文档,最后调研报告的代码就写在这里了,注解预编译后的代码结构和编译前的差距很大)所影响,让你很难看到你想看到的,此时来直接学习Python的协程高级编程吧
第七步:
Python协程开发,在JS的基础上,更加暴露了协程运行和启动的全过程,包括事件循环服务的启动,刚才说的多个生成器互相调用这个事情到底谁来做,每个生成器都开一个服务或者队列来搞是不合理的,不但维护复杂,还会出现争抢数据的概念,统一的服务调度以及保证任务的先后顺序是必须的,这时你就发现了channel(有类型的队列)这个好东西,以及Python为你量身定制的Loop协程服务
第八步:
再学习Python的时候,你会发现Python的面向对象有点怪怪的,感觉是不完全版的面向对象,你可能认为是你Java语言先入为主了,请别这样想,你的每次发现都源自于你的每一次敏感,Python的面向对象肯定是后面加上去的。
从Python历史来看,面向对象的下发,Javascript和Python竟然如此相似,都是后来加上去的,他们不推崇Java重度的反程序员的面向对象的开发模式,因为没有面向对象可以让代码更简洁灵活,有了面向对象,就让程序员束手束脚,编程语言本来就是程序员的工作,现在却变成了码农的一亩三分地,这地不是你想怎么造就怎么造的,土地没有地主的时候,原始人是可以随便造的,但有了资本家,我们就要规整起来。
说偏了,之所以提出这个引子,是因为Python的成员函数都有一个self,这个在java和php中都是隐藏起来的,它却暴露了,这是个什么鬼?这玩意可以造吗?
目前我Python掌握还浅,但我知道PHP的成员函数可以动态挂,而且已有的成员函数还可以挂在其他对象上,你是不是从未想过这个事情?
为何可以乱挂呢?因为指针呀!
如果将函数看做一个独立的对象,对象看成一个结构体,也即只有key:value的结构体,那么结构体里面只要有一个指针指向这个函数,那么两者就有了归属关系,也即形成了结构体可以调用函数,同时结构体把自己的指针传给函数,函数就可以使用结构的数据了。
你是不是又想卧槽,当然,这篇文档只是写给PHPer的,如果C语言大佬,会觉得不屑一顾,但C语言大佬估计也没学那么多语言吧。
绕了一个湾,就是想说明一件事,类原来是函数+结构体+两者关系的一种语法糖写法,底层帮你做了一些事情。
因此Python的self就相当于一个指针,函数来承接这个指针,找到结构体中的数据。
同时为了保证一对多的方式,减少结构体本身的侵入性,我们不可能为了每个新增函数都设置一个指针指向函数吧,应该是谁用我,谁来找我的概念,所以结构体是不需要变动的,函数只要传递结构体的指针即可。
你知道我说了什么吗?我说了Go语言的面向对象,这么你想,你觉得Go大佬为何要把Go设计的那么简陋了吗?因为么有必要搞一套预编译器,重造一套Java呀,你喜欢Java的设计模式,就直接用Java还用毛Go。
第九步:
此时你终于明白为何Python的每个成员函数都要传入self这个变量了,就是非完全版的面向对象,另外基于此,你会推演出来另外一个概念,那就是JS一切皆对象,Python一起皆对象,既然函数是独立的,难道Python函数也可以搞属性,是的!Python的函数本身就是一个对象,它可以给自己增加属性的,卧槽,这货竟然跟JS一样,至此,你已经不再将函数和结构体混为一谈了,但是鉴于Python不暴露指针给你,你没机会随意这样搞关联,没关系,让你随意的Go语言来了
第十步:
Go语言直接将这个概念彻底暴露了,连class这种东西都懒得搞了,直接上结构体和函数,你只要函数里面传递这个结构体的指针或者实例就行,这真是直接把C语言给拿过来用了,但是C语言比你想的更简陋,人家没有string的处理,更没有动态数组,甚至map的概念,Go语言将这些重复乏味的过程封装成了工具包,形成了Go语言,至此你已经可以推测Go语言的大致写法和逻辑了,那个for因为继承自C语言,所以新创造的Map,切片 for是搞不了的,怎么办?Go给了你一个语法糖range让你统一处理这些可迭代的数据类型
第十一步:
如果Go仅仅封装了C,或者给C弄了一个语法糖,那直接用C不是更好,不过最核心的竞争力在于第三步提到的,如果我在开发高并发的时候,不必去思考是使用线程还是协程,那我不是更加省心吗?是的,Go也想到了,于是它用GMP的设计方式,统合了进程线程协程,推出了goroutine,此时,再也别把Go理解为PHP的取代品了,这货压根跟PHP没有任何相关性,如果上来就搞Go,估计搞几年也都是将PHP的CRUD搬迁为Go的CRUD,如果仅仅是为了CRUD,用PHP恐怕是这个世界最简单的方式了,如果你觉得不快?那为何不让Go充当你PHP的底层,帮助PHP执行I/O阻塞式的计算逻辑呢
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)