cpu:内核
进程:
优点:提高效率,利用cpu多核优势 开启个数:理论上是cpu内核的1-2倍 描述:一段程序或者脚本的执行,cpu资源分配的最小单位 缺点:资源消耗非常大,进程过多,cpu切换进程执行也消耗资源,资源共享困难 使用场景:cpu密集型应用程序(计算密集型)
线程:
优点:提高效率,资源共享 开启个数:跟计算硬件有关系,跟应用场景有关系,一般高于可开启进程守数 描述:进程下可以开启多个线程,cpu调度的最小单位 缺点:开启个数也不是无限的,如果开启过多,造成进程瘫痪 使用场景:IO(input(response),ouput(request),)密集型应用程序,爬虫成是网络IO,长延时操作
跨平台的进程创建模块(multiprocessing)
multiprocessing模块提供了一个Process类来代表一个进程对象,下面的例子演示了启动一个子进程并等待其结束
步骤:
1、从multiprocessing模块导入Process类
2、进程的操作(1)创建和启动创建子进程时,只需要传入一个执行函数和函数的参数,创建一个Process实例,用start()方法启动,这样创建进程比fork()还要简单。创建格式:p=Process(target=函数名)启动格式:进程对象名.start()
参数列表:Process([group [, target [, name [, args [, kwargs]]]]])
target:表示这个进程实例所调用对象;
args:表示调用对象的位置参数元组;
kwargs:表示调用对象的关键字参数字典;
name:为当前进程实例的别名;
group:大多数情况下用不到;
#单进程代码:
#参数的使用
#多进程
多进程资源共享
进程池的使用
当需要创建的子进程数量不多时,可以直接利用multiprocessing中的Process动态成生多个进程,但如果是上百甚至上千个目标,手动的去创建进程的工作量巨大,此时就可以用到multiprocessing模块提供的Pool方法。初始化Pool时,可以指定一个最大进程数,当有新的请求提交到Pool中时,如果池还没有满,那么就会创建一个新的进程用来执行该请求;但如果池中的进程数已经达到指定的最大值,那么该请求就会等待,直到池中有进程结束,才会创建新的进程来执行,请看下面的实例:腾讯招聘多进程爬虫
多线程基本使用
线程类的使用
互斥锁
上锁解锁过程
当一个线程调用锁的acquire()方法获得锁时,锁就进入“locked”状态。每次只有一个线程可以获得锁。如果此时另一个线程试图获得这个锁,该线程就会变为“blocked”状态,称为“阻塞”,直到拥有锁的线程调用锁的release()方法释放锁之后,锁进入“unlocked”状态。线程调度程序从处于同步阻塞状态的线程中选择一个来获得锁,并使得该线程进入运行(running)状态
总结
锁的好处:
地执行锁的坏处:
在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁。尽管死锁很少发生,但一旦发生就会造成应用的停止响应。下面看一个死锁的例子:有两个人分别做“西兰花”和“红烧肉”,每个人都需要“铲子”和“锅”之后才能炒菜。
代码
避免死锁
程序设计时要尽量避免(银行家算法)
添加超时时间
线程池
线程类的爬虫
ThreadLocal
在多线程环境下,每个线程都有自己的数据。一个线程使用自己的局部变量比使用全局变量好,因为局部变量只有线程自己能看见,不会影响其他线程,而全局变量的修改必须加锁。全局变量local_school就是一个ThreadLocal对象,每个Thread对它都可以读写student属性,但互不影响。你可以把local_school看成全局变量,但每个属性如local_school.student都是线程的局部变量,可以任意读写而互不干扰,也不用管理锁的问题,ThreadLocal内部会处理。可以理解为全局变量local_school是一个dict,不但可以用local_school.student,还可以绑定其他变量,如local_school.teacher等等。ThreadLocal最常用的地方就是为每个线程绑定一个数据库连接,HTTP请求,用户身份信息等,这样一个线程的所有调用到的处理函数都可以非常方便地访问这些资源。
一个ThreadLocal变量虽然是全局变量,但每个线程都只能读写自己线程的独立副本,互不干扰。ThreadLocal解决了参数在一个线程中各个函数之间互相传递的问题。
协程
首先我们得知道协程是啥?协程其实可以认为是比线程更小的执行单元。 为啥说他是一个执行单元,因为他自带CPU上下文。这样只要在合适的时机, 我们可以把一个协程 切换到另一个协程。 只要这个过程中保存或恢复 CPU上下文,那么程序还是可以运行的。通俗的理解:在一个线程中的某个函数,可以在任何地方保存当前函数的一些临时变量等信息,然后切换到另外个函数中执,注意不是通过调用函数的方式做到的,并且切换的次数以及什么时候再切换到原来的函数都由开发者确定。
在IO密集型的程序中由于IO操作远远慢于CPU的操作,所以往往需要CPU去 等IO操作。 同步IO下系统需要切换线程,让操作系统可以在IO过程中执行其他的东西。这样虽然代码是符合人类的思维习惯但是由于大量的线程切换带 来了大量的性能的浪费,尤其是IO密集型的程序。所以人们发明了异步IO。就是当数据到达的时候触发我的回调。来减少线程 切换带来性能损失。 但是这样的坏处也是很大的,主要的坏处就是操作被 “分片” 了,代码写的不是 “一气呵成” 这种。而是每次来段数据就要判断数据够不够处理哇,够处理就处理吧,不够处理就在等等吧。这样代码的可读性很低,其实也不符合人类的习惯。但是协程可以很好解决这个问题。例如把一个IO操作 写成一个协程。当触发 IO操作的时候就 动让出CPU给其他协程。要知道协程的切换很轻的。 协程通过这种对异步IO的封装 既保留了性能也保证了代码的容易编写和可读性。