代码的性能优化,有些是从逻辑层面进行的,比如同时对50W个人发放奖励,可以改成用户登录的时候,自动领取有没奖励,或者统计每日的每个业务员的销售额和实时累积的销售额,将实时sum函数改成,每一笔单独存在某个字段里面。有些是从硬件层面优化的,比如增加服务器的内存空间和内核数量。而有些是从代码的运行线程考虑的,只要有等待时间,我们将其放入等待仓库,让不用等待的线程先跑。
所以我们需要理解线程的逻辑。这个关系到语种的差距,还有深刻理解为什么php比java这种语言种类的差距。如果把进程看成是一座工厂,这个工厂是要完成目标的生产任务,在这个工厂里面,有厂房(内存空间)仓库(文件资源),每条流水线都在进行生产任务的生产。而这里的每条流水线,就是线程。在常规的PHP里面,在传统的网站,基本就是一个访问,直接请求对应了一个线程,独占了这个工厂。如果需要在这个工厂里面管理多条生产线,PHP是无能为力的,所以这个PHP在占据了方便和效率高,但是整体市场也在逐步萎缩。而swoole和workerman就是在这个方便做出努力。
在这个过程里面,每一条生产线的运行都是需要执行时间的。而且在执行A线程的时候,B/C/D线程都只能等待排队,这就严重的制约了其并发性能。A/B/C/D任务基本都差不多,除了请求参数有不同,其他逻辑完全一样。但是PHP没有响应过来的情况下,只能排队执行。这里以一个最常见的爬虫为例子,我们要抓取10W个页面的数据,需要采集页面后,做一些列逻辑处理,最后存储。按照PHP的做法,我们只能一条路走到黑,或者写10个PHP文件,执行10次PHP,每个PHP的采集范围都不同。
而如果切换到python/node/go/java里面,我们只需要运行一个文件,直接开启10个线程,同时让10个线程分别采集不同范围的页码即可。类似的场景,相同事情必须另外开进程(也就是开工厂,重新部署一条生产线),这个代价就非常大。从这里可以看出,多线程可以提高CPU的利用率,加快程序的响应速度,同时提高程序的并发能力,更加有效利用系统资源。
然后马上有个疑问,多线程这么牛逼的能力,为啥PHP没有呢?这与PHP本身早起的理念关系就很大,PHP就是追求简单和快速,引入了多条生产线,在现实工厂里面需要额外增加管理人员,还要解决不同生产线同时用到了仓库互相吵架怎么处理,同时用到了厂房又怎么协调,最后组装产品的时候,又怎么管理。
而在程序里面,表现出来就是增加了内存开销,毕竟,每个线程需要记录线程寄存器等信息,线程太多,会导致内存消耗过大,上下文的切换开销,资源竞争问题,俩个线程同时使用到了一个东西需要处理,还有调试和测试复杂度也提升,因为线程执行顺序可能有影响,代码错误如果与线程执行顺序有关联,会导致代码出现的错误可能出现幽灵错误,就是有时候有有时候又没有。
想到这里,其实我们就能理解到我们平时遇到的一些只发生过一次的错误,很可能就是多线程异常产生的。而这些在PHP理念里面,都是非常繁琐的,它直接将这个多线程概念摒弃了。这样导致了,我们学习使用PHP非常的爽,入门很快,学习也很快,但是劣势就是爽过了,发现很多东西都受到了限制。导致不得不通过其他的语言来替代。
到这里,其实已经大体解释清楚了进程和线程的关系,还有多线程和单线程的优势和劣势。包括PHP本身设计的简易思路,放弃了多线程的管理而提升自己的简单程度。 最后我们来使用python实现一个简单的多线程,尝试着跑一遍代码程序,彻底理解多线程过程里面到底是怎么跑的,我们写爬虫的时候,特别是一些静态页面的采集请求,就可以采用多线程的方式执行,
import threading
import time
# 定义一个函数,用于在线程中执行
def print_numbers():
for i in range(5):
time.sleep(1) # 模拟一些耗时的操作
print(f"Thread 1: {i}")
# 定义另一个函数,用于在另一个线程中执行
def print_letters():
for letter in ['A', 'B', 'C', 'D', 'E']:
time.sleep(3) # 模拟一些耗时的操作
print(f"Thread 2: {letter}")
# 创建两个线程
thread1 = threading.Thread(target=print_numbers)
thread2 = threading.Thread(target=print_letters)
# 启动线程
thread1.start()
thread2.start()
# 等待所有线程完成
thread1.join()
thread2.join()
print("Both threads have finished executing.")