# 程序 <hr style="height:1px;border:none;border-top:1px ridge lime;" /> > 一个程序至少一个进程,一个进程至少一个线程,线程不能独立执行,必须依存在进程中 ### 变革 #### <font color='green'>第一次变革</font> 最开始的时候,为了实现一个服务器可以支持多个客户端连接,人们想出了fork/thread等办法,当一个连接来到的时候, 就fork/thread一个进程/线程去接收并且处理请求,然而,当时估计是大家都穷吧,没啥钱买电脑,所以这个模型一直很 好用,过去几十年都没有问题。 #### <font color='green'>第二次变革</font> 时代发展了,越来越多的用户进行网络连接(其实也没多少),但是之前的fork/thread模型就不行了,太辣鸡了,发明了 一种叫做「IO多路复用」的模型,这种模型的好处就是「没必要开那么多条线程和进程了」,一个线程一个进程就搞定了。 一个连接来了,就必须遍历所有已经注册的文件描述符,来找到那个需要处理信息的文件描述符,如果已经注册了几万个文 件描述符,那会因为遍历这些已经注册的文件描述符,导致cpu爆炸。 #### <font color='green'>第三次变革</font> 互联网时代爆炸,数以千万计的请求在全世界范围内发来发去,服务器大爆炸,人们通过改进「IO多路复用」模型,进一步的 优化,发明了一个叫做epoll的方法。 ### 术语 #### 进程与程序 - 程序:编写完毕的代码,在没有运行的时候,称为程序 - 进程:正在运行着代码,称为进程 > 进程,除了包含代码以外,还有需要运行的环境等,所以与程序是有区别的 #### 并发与并行 - 好比告诉公路4车道,同一时刻,所有车辆可以互不干扰的在自己的车道上行驶,称为并行 - 解决办法,增加了10车道,在不行增加100车道(但这样增加下去不是办法) - 水平扩展 - 也是同时做某些事,但是强调是同一个时间段做了几件事,车辆同时要经过路面,这就是并发 - 排队(天然解决并发的办法) - 争抢 单核cpu同时运行多个程序原理:比如qq先运行2微妙,然后扔出去,在把微信拿过来运行2微妙,在扔出去,即将程序运行的时间分成一片一片的时间,操作系统任务就是来负责切换不同的程序,即时间片轮转还有一种叫优先级调度 > 并发是一个人同时吃三个馒头,而并行是三个人同时吃三个馒头。在工作中我们都说并发,不说并行 ###### 解决高并发 1.队列, 缓冲区: - 队列: 即排队. - 缓冲区: 排成的队列. - 优先队列: 如果有男生队伍和女生队伍,女生队伍优先打饭,就是优先队列. 2.争抢: - 锁机制: 争抢打饭,有人抢到,该窗口在某一时刻就只能为这个人服务,锁定窗口,即锁机制. - 争抢也是一种高并发解决方案,但是有可能有人很长时间抢不到,所以不推荐. 3.预处理: - 统计大家爱吃的菜品,最爱吃的80%热门菜提前做好,20%冷门菜现做,这样即使有人锁定窗口,也能很快释放.这是一种提前加载用户需要的数据的思路,预处理思想,缓存常用. 4.并行: - 开多个打饭窗口,同时提供服务. - IT日常可以通过购买更多服务器,或多开线程,进程实现并行处理,解决并发问题. 这是一种水平扩展的思路. 5.提速: - 通过提高单个窗口的打饭速度,也是解决并发的方式. - IT方面提高单个CPU性能,或单个服务器安装更多的CPU. - 这是一种垂直扩展的思想. 6.消息中间件: - 如上地地铁站的九曲回肠的走廊,缓冲人流. - 常见消息中间件: RabbitMQ, ActiveMQ(Apache), RocketMQ(阿里Apache), kafka(Apache)等 #### 阻塞非阻塞 当你把水放到水壶里面,按下开关后,你可以坐在水壶前面,别的事情什么都不做,一直等着水烧好。你还可以先去客厅进行看电视、打扫卫生等活动,等着水开就好了。对于你来说,坐在水壶前面等就是阻塞的,去客厅做其他事情等着水开就是非阻塞的 - 阻塞请求,A调用B,A一直等着B的返回,别的事情什么也不干。 - 非阻塞请求,A调用B,A不用一直等着B的返回,先去忙别的事情了。 > 阻塞、非阻塞说的是<font color='red'>调用者</font>,常见的阻塞形式有:网络 I/O 阻塞、磁盘 I/O 阻塞、用户输入阻塞等。阻塞是无处不在的,包括 CPU 切换上下文时,所有的进程都无法真正干事情,它们也会被阻塞。如果是多核 CPU 则正在执行上下文切换操作的核不可被利用 #### 同步异步 在很久之前,科技还没有这么发达的时候,如果我们要烧水,需要把水壶放到火炉上, 我们通过观察水壶内的水的沸腾程度来判断水有没有烧开。随着科技的发展,现在市面上的水壶都有了提醒功能,当我们把水壶 插电之后,水壶水烧开之后会通过声音提醒我们水开了。对于烧水这件事儿来说,传统水壶的烧水就是同步的,高科技水壶的烧水就是异步的。 - 同步请求:A调用B,B的处理是同步的,在处理完之前他不会通知A只有处理完之后才会明确的通知A。 - 异步请求:A调用B,B的处理是异步的,B在接到请求后先告诉A我已经接到请求了,然后异步去处理,处理完之后通过回调等方式再通知A。 > 同步和异步最大的区别就是被调用方的执行方式和返回时机。同步指的是被调用方做完事情之后再返回,异步指的是被调用方先返回, 然后再做事情,做完之后再想办法通知调用方 #### 进程线程 - 以多进程形式,允许多个任务同时运行 - 以多线程形式,允许单个任务分成不同的部分运行 - 提供协调机制,一方面防止进程间和线程间产生冲突,另一方面允许进程之间和线程之间共享资源 ###### 区别 - 进程 - 进程是资源分配的最小单位 - 进程之间的通信需要以通信的方式(IPC)进行,需要考虑进程之间通信 - 进程有自己的独立地址空间,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵 - 多进程程序更健壮,基于自己有独立的地址空间特性一个进程死掉并不会对另外一个进程造成影响 - 线程 - 线程是程序执行的最小单位 - 多线程程序只要有一个线程死掉,整个进程也死掉了 - 线程之间的通信更方便,同一进程下的线程共享全局变量、静态变量等数据,但是处理好同步与互斥是编写多线程程序的难点 - 线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小很多,同时创建一个线程的开销也比进程要小很多 ###### 联系 - 进程是很久以前的计算机模型,它当时是最小的单元单元 - 线程是后来设计的计算机模型,它是现在最小的执行单元 在运行程序的时候,操作系统管理进程,进程中存在线程,其实是进程管理线程,如果具有多个线程,那么主线程就会管理子线程,在运行程序的时候,最开始就是一个进程一个主线程,主线程会管理子线程在操作系统中,我们可以直接查询到进程号,也就是进程的PID,可以直接杀死一个进程,但是我们不能直接杀死一个线程 ###### 选择 在这个问题上,首先要看下你的程序是属于哪种类型的。一般分为两种CPU密集型和I/O密集型。 - CPU密集型:程序比较偏重于计算,需要经常使用CPU来运算。例如科学计算的程序,机器学习的程序等。 - I/O密集型:顾名思义就是程序需要频繁进行输入输出操作。爬虫程序就是典型的I/O密集型程序。 如果程序是属于 CPU 密集型,建议使用多进程。而多线程就更适合应用于I/O密集型程序。 大多情况下,要用到多进程多线程的主要是需要处理大量的IO操作或处理的情况需要花大量的时间等等,比如:读写文件、视频图像的采集、处理、显示、保存等 如果分不清任务是CPU密集型还是IO密集型,我就用如下2个方法分别试: ```python from multiprocessing import Pool from multiprocessing.dummy import Pool ``` 哪个速度快就用那个。从此以后我都尽量在写兼容的方式,这样在多线程/多进程之间切换非常方便。 在这里说一个我个人的经验和技巧:现在,如果一个任务拿不准是CPU密集还是I/O密集型,且没有其它不能选择多进程方式的因素,都统一直接上多进程模式 #### 队列 - 先入先出 - 后入先出 - 设置优先级 ```python import queue # 先入先出 q = queue.Queue() q.put(1) q.put(2) print(q.get()) print(q.get()) # 后入先出 q = queue.LifoQueue() q.put(1) q.put(2) print(q.get()) print(q.get()) print(q.get()) # 设置优先级 q = queue.PriorityQueue() q.put((-1,"chenronghua")) q.put((3,"hanyang")) q.put((10,"alex")) q.put((6,"wangsen")) print(q.get()) print(q.get()) print(q.get()) print(q.get()) ``` #### 生产者消费者 在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。 ##### 为什么要使用生产者和消费者模式 在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这个问题于是引入了生产者和消费者模式。 ##### 什么是生产者消费者模式 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。 ```python import time,random import queue,threading q = queue.Queue() def Producer(name): count = 0 while count <20: time.sleep(random.randrange(3)) q.put(count) print('Producer %s has produced %s baozi..' %(name, count)) count +=1 def Consumer(name): count = 0 while count <20: time.sleep(random.randrange(4)) if not q.empty(): data = q.get() print(data) print('\033[32;1mConsumer %s has eat %s baozi...\033[0m' %(name, data)) else: print("-----no baozi anymore----") count +=1 p1 = threading.Thread(target=Producer, args=('A',)) c1 = threading.Thread(target=Consumer, args=('B',)) p1.start() c1.start() ```