【黑马-python进阶】---学习笔记(7)---线程、进程、协程、正则表达式

4 多任务—线程

4.1 多任务介绍

  • 目标

    • 知道多任务概念
    • 多任务和单任务程序的区别
  • 1、多任务解析

    • 操作系统可以同时运行多个任务,现在,多核CPU已经非常普及,但是,即使过去的单核CPU,也可以执行多任务。!!!通过时间片轮转的方式,单核多任务!!!

image-20220518194604318

  • 2、多任务表现形式

    • window下打开任务管理可以很清晰看到多个进程同时执行任务,qq,微信都是以进程的形式寄存在window下,大多我们在协议一些控制台程序真正执行的时候都是以进程调度;
  • 3、Python默认是单任务
    image-20220518194632803
    image-20220518194657758* 小结

1、python默认为单任务;

2、多任务:同一时间多个任务同时执行;

4.1.2 多任务-线程

4.2 线程基础

4.2.1 线程-基本使用
  • 目标

    • 理解主线程和子线程关系;
    • 使用threading.Thread类能够创建线程对象;
    • threading.Thread的target参数能够指定线程执行的任务;
  • 1、线程概念

    • 线程,称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
    • 一个标准的线程由线程ID,当前指令指针PC,寄存器集合和堆栈组成。
    • 线程是进程中的一个实体,是被系统独立调度和分派的基本单位。
    • 线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。
    • image-20220518194820530
    • 主线程默认就有,子线程需要自己用代码写出来才有
    • 主线程:程序创建就会有一个主线程

​ 当一个程序启动时,就有一个进程被操作系统(OS)创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程。

主线程重要性:

1)是产生其他子线程的线程;

2)通常它必须最后完成比如执行各种关闭动作;

  • 子线程

程序执行的一条分支,当子线程启动后会和主线程一起同时执行;

  • 2、单线程执行
    image-20220518194941677
  • 3、使用threading模块 创建子线程
    image-20220518195014971
    image-20220518195029631
  • 线程对象只有调用了start()子线程才会执行!
  • 子线程启动时,主线程一直都在执行!!
4.2.2 线程名称、总数量
  • 目标

    • 如何查看正在活动的线程数量
  • 1、查看线程数量

    • **threading.enumerate()**获取当前所有活跃线程的列表,使用len()对列表长度可以看到目前活跃的线程image-20220518195316712image-20220518195325964
  • 2、线程名称

    • threading.current_thread()可以获取当前线程对象(含名称)
      image-20220518195339149
4.2.3 线程-线参数及顺序
  • 目标

    • 向线程函数传递多个参数;
    • 多线程执行的顺序特点;
  • 1、多线程中参数传递问题

    • 改进sing函数,增加a\b\c三个参数;
def sing(a,b,c):
    print("----sing------%s---%s---%s"%(a,b,c))
for i in range(5):
    print("sing...")
    time.sleep(0.5)
    
t1=threading.Thread(target=sing)
  • 传递参数的方法一:
    • 使用args(元组)传递参数;
t1=threading.Thread(target=sing,args=(10,20,30))
  • 传递参数的方法二:
    • 使用kwargs(字典)传递参数;
t1=threading.Thread(target=sing,kwargs={"a":10,"b":20,"c":30})
  • 传递参数的方法三:
    • 同时使用args和kwargs传递参数
    • 元组里只有一个参数时,最后要加一个,不然会被认为是10
t1=threading.Thread(target=sing,args=(10,),kwargs={"b":20,"c":30}
  • 2、线程的执行顺序
    image-20220518195458866
    image-20220518195507418
  • 说明:

(1)多线程的执行顺序是不确定的;

(2)当执行到sleep语句时,线程将被阻塞(Blocked),到sleep结束时,线程进入到就绪状态,等待调度。而线程调度将自行选择一个线程执行。

(3)保证每个线程都运行完整,但是线程的启动顺序、run函数中每次循环的执行顺序都不能确定;

  • 3、总结

(1)每个线程默认与一个名字,尽管没有指定线程对象的name,但是python会自动为线程制定一个名字;

(2)当线程的run()方法结束时,该线程完成;

(3)无法控制线程调度程序,但可以通过别的方式影响线程调度的方式;

4.2.4 守护线程
  • 目标

    • 使用setDaemon设置子线程守护主线程;
  • 1、守护线程

    • 如果在程序中将子线程设置为守护线程,则该子线程会在主线程结束时自动退出,设置方式为**thread.setDaemon(True),要在thread.start()之前设置,默认是false,就是主线程结束时,子线程依然在执行;**
    • 下列代码就是,主线程已经exit()【其实没有真正结束】,但是子线程还在继续执行;
      image-20220518195546367
  • 2、设置守护线程

设置守护线程(如果主线程结束了,也随之结束)

线程.setDeamon(True)
image-20220518195605885

4.3 并行和并发

  • 目标
    • 明白并行和并发的区别
  • 1、多任务原理剖析
    • image-20220518195637136
  • 2、并行和并发
    • 并发:
      • 任务数多于cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行;
      • 真正的并行执行多任务只能在多核CPU上实现,由于任务数量远大于CPU的核心数量,因此,操作系统会自动把很多任务轮流调度到每个核心上执行。

image-20220518195723945

  • 并发:
    • 任务数小于等于CPU核心,任务真的一起执行;
      image-20220518195739578

4.4 自定义线程类

  • 目标

    • 通过继承**threading.Thread**可以实现自定义线程;
  • 1、线程执行代码的封装

    • 通过使用threading模块能完成多任务的程序开发,为了让每个线程的封装性更完美,往往会定义一个新的子类class,

    • 1、让自定义类继承threading.Thread

      2、让自定义类重写run方法;

      3、通过实例化自定义类对象.start()方法启动自定义线程;

    • 代码实现:
      image-20220518195756011
      image-20220518195812786

  • 说明:

(1)python的threading.Thread类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。

​ 而创建自己的线程实例后,通过Tread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,会调用run方法执行线程;

(2)在run方法中,使用self.name可以获取当前正在运行的线程名称;

  • 自定义线程执行原理:

    • start()调用了run()方法;
      image-20220518195841530
  • 2、自定义线程类应用场景

    • 多线程实现文件下载
      image-20220518195854760
      image-20220518195927268
      image-20220518195936706
  • 多线程实现爬虫
    image-20220518195944333
    image-20220518195959978
    image-20220518200013860
    image-20220518200022303

4.5 多线程-全局变量

4.5.1 多线程-共享全部变量
  • 目标

    • 多线程能够共享全局变量数据
  • 1、多个线程方法中可以共用全局变量
    image-20220518200036103
    image-20220518200042926

  • 2、列表作为实参传递到线程中
    image-20220518200052360

  • 小结

1)在一个进程内的所有线程共享全局变量,多个线程内共享数据;

2)缺点:线程是对全局变量随意篡改可能造成多线程之间对全局变量的混乱,即线程非安全

4.5.2 多线程-共享全局变量的问题
  • 目标

    • 多线程共享全局变量数据会导致资源竞争问题
  • 1、多线程共享变量遇到的问题
    image-20220518200109428
    image-20220518200125442image-20220518200144005

  • 2、产生原因
    image-20220518200204561

  • 3、处理方式–join()让某一个线程优先处理
    image-20220518200219903

4.6 同步与锁

4.6.1 同步
  • 1、同步与异步

    • 同步:多任务
      • 多个任务之间执行有先后顺序,必须一个执行后,另一个才可以继续执行,只有一个主线;
    • 异步:
      • 多个任务之间没有先后顺序,可以同时运行,存在多条运行主线,互不影响;
  • 2、解决线程同时修改全局变量的方式
    image-20220518200234212

  • 线程锁机制
    image-20220518200244319

4.6.2 互斥锁
  • 1、互斥锁概念

    • 当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。
    • 互斥锁为资源引入状态:锁定/非锁定
    • 某个线程要更改共享数据时,先将其锁定,此时的资源状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态改为“非锁定”,其他的线程才能再次锁定该资源,
    • 互斥锁保证了每次只有一个线程写入操作,保证多线程数据的正确性;
  • threading模块定义了Lock类,方便处理锁定
    image-20220518200300388
    image-20220518200315455

  • 2、互斥锁:100万次加操作
    image-20220518200325567
    image-20220518200336447

  • 3、上锁解锁过程
    image-20220518200353951

  • 第一个Printf的线程已经加完了自己的100,0000;剩下不到200,0000是另一个线程正在加;

  • 小结
    image-20220518200424710

4.6.3 死锁
  • 1、死锁
    • 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁;
    • **尽管死锁很少发生,**一旦发生就会造成应用的停止响应;

image-20220518200441903
image-20220518200502480
image-20220518200516045

  • 2、解法办法
    image-20220518200526553

4.7 多任务版udp聊天器

4.13.1 同时收发消息
  • 1、程序分析

    • 说明

      • 编写一个有2个线程的程序;
      • 线程1:接收数据并显示;
      • 线程2:检测键盘数据然后通过udp发送数据;
    • 改进思路

      • 1、单独分开子线程用于接收消息,以达到收发消息可以同时进行;
      • 2、接收消息要能够连续接收多次;
      • 3、设置子线程守护主线程;(解决无法正常退出的问题);
  • 2、参考代码
    image-20220518200554824
    image-20220518200607442

4.13.2 支持接收多条信息+while True

image-20220518200635446
image-20220518200644734

4.8 TCP服务端框架

  • 目标
    • 使用多线程实现同时接受多个客户端的多条信息;

之前是一个用户连接后,一直发消息,一直到用户断开连接,才可以与其他用户建立连接,开始发消息;

  • 1、TCP服务器端

1、实现指定端口监听;

2、实现服务器端地址重用,避免"Address already in use"错误;

3、支持多个客户端连接;

4、支持不同的客户端同时收发消息(开启子线程);

5、服务器端主动关闭服务后,子线程随之结束;
image-20220518200657288
image-20220518200706029

  • 两个while True可以保证,可以有多个用户建立socket,形成socket连接后,发送多条信息;

    • 但是上述情况,客户端1和2可以均连接,但是在同一个时刻,只有先建立连接发送消息的客户端1可以发送多条信息,只有当客户端1结束发送之后,才会跳出while True;第二个客户端2才可以发送信息;
  • 单独开一个子线程,线程—多任务;

    • 两个不同的recv_msg在同时运行;
    • 来一个客户端就开一个线程,之间互不干扰;
  • listen(128)在window真限制,在linux中不会真的限制

5 多任务-进程

5.1 进程基础

5.1.1 进程及其状态
  • 进程也是实现多任务的一种方式
  • 1、进程概念

    • 进程是资源分配的最小单元,是线程的容器,程序隔离的边界;
    • !!!线程:是cpu调度的最小单位;
      image-20220518201836945
  • !!程序运行之后,会有一个进程=程序+资源!!

  • 2、进程的状态

工作中,任务数往往大于cpu的核数,即一定有一些任务正在执行,而另外一些任务在等待cpu进行执行,因此导致有了不同的状态;
image-20220518202013564

5.1.2 进程的创建-multiprocessing
  • 目标
    • 知道使用Mutiprocessing.Process类能创建进程对象;
    • 能够multiprocessing.Process的target参数能够指定进程执行的任务的函数
  • multiprocessing模块就是跨平台版本的多进程模块

    • 提供了⼀个Process类来代表⼀个进程对象,这个对象可以理解为是⼀个独⽴的进程, 可以执⾏另外的事情
  • 1、创建子进程

image-20220518202358060

  • !!!程序启动后有一个默认的主进程!!!在主进程之中才有主线程!!!
  • !!!目前是在主进程中创建一个主线程!!!

image-20220518202417771

5.1.3 进程名称、Pid
  • 目标
    • 使用getpid和getppid的获取进程id和进程父id
  • 1、进程名称获取
    image-20220518202616045
  • 2、进程pid
    image-20220518202709398
    image-20220518202718189
  • 3、Kill-9 杀掉进程
    image-20220518202755332
  • 小结
    image-20220518210013002

5.2 进程-参数传递、全局变量、守护进程

5.2.1 参数传递,全局变量
  • 目标
    • 多进程之间不能共享全局变量;
    • 给子进程指定函数传递参数;
  • 1、子进程参数传递
    image-20220518210427112

  • 2、进程间不共享全局变量

  • 子进程是复制了一份全局变量,原来的全局变量一直没有改变
    image-20220518212308000
    image-20220518212027280

  • 小结
    image-20220518212410453

5.2.2 守护进程
  • 目标
    • 通过daemon设置子进程守护主进程
  • 1、守护主进程
    • p1.daemon=True 设置⼦进程 p1 守护主进程, 当主进程结束的时候, ⼦进程也随之结束
    • p1.terminate() 终⽌进程执⾏, 并⾮是守护进程
      image-20220518210638461
      image-20220518210646749

5.3 进程、线程对比

  • 1、功能

    • 进程:完成多任务,一台电脑上同时运行多个QQ;
    • 线程:完成多任务,一个QQ中多个聊天窗口;
      image-20220518213130701
  • 2、使用区别

    • 1)进程是系统进行资源分配和调度的一个独立单位;
    • 2)线程是进程的一个实体,是cpu调度的基本单位,它是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他线程共享进程所拥有的全部资源;
    • **3)**一个程序至少有一个进程,一个进程至少有一个线程;
    • **4)**线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性性高;
    • **5)**进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率;
  • image-20220518213601159

    • **6)**线程不能独立执行,必须依存于进程中;
    • **7)**可以将进程理解为工厂的一条流水线,线程就是这个流水线上的工人;
  • 3、进程与线程的选择取决于以下几点

image-20220518213801358
image-20220518214357474

  • 注意:实际上基本上都是“进程+线程”的结合方式,

  • 小结
    image-20220518214839862

5.4 消息队列

5.4.1 基本操作
  • 目标
    • 明白queue的put()和get()的作用;

Process之间有时需要通信,操作系统提供了很多机制来实现进程间的通信;

  • 1、Queue介绍
    • 使用multiprocessing模块的Queue实现多进程之间的数据传递;
    • Queue本身是一个消息队列程序
      image-20220518215156921
  • 2、Queue基本使用
    image-20220518215409315
  • put大于队列长度时,会一直等待,程序一直运行不会结束;
  • 如果是选择put_nowait就会直接报错,不等待;
    image-20220520154151266
5.4.2 常见判断
  • 目标
    • 说出queue的full()和empty()的作用;
    • 使用qsize()获取队列中的消息的个数;
5.4.2 常见判断
  • 目标
    • 说出queue的full()和empty()的作用;
    • 使用qsize()获取队列中的消息的个数;
  • 1、消息数量、判断是否为空、判断是否已满
    • 消息数量qsize(),注意队列每get()一次,数量就会-1;
      image-20220520155847508
  • empty()判断队列是否为空
    image-20220520155910763
    image-20220520155918201

!!!解决办法:time.sleep(0.001)稍微休眠一下;!!!

不然就会出错,保险起见,可以使用qsize()判断!!!

  • full()判断队列是否已满,消息数量达到设定上限,则为满;
    image-20220520160038105
5.4.3 Queue实现进程间通信
  • 目标
    • 使用Queue队列能实现进程间数据共享
  • 1、进程间通信思路
    image-20220520161656352
  • 2、Queue实现进程间通信
    image-20220520161716659
    image-20220520161728914
  • 3、Queue使用过程中常见错误
    • 1)qsize()报错
    • image-20220520161830500
  • 2)ImportError:cannot import name ‘Queue’
  • 小结
    image-20220520164110840

5.5 进程池Pool

5.5.1 进程池基础
  • 目标
    • 进程池的作用
  • 1、进程池概述—批量生成进程
    image-20220520164828713
  • 2、核心方法
    image-20220520164957658
  • 同步:同一时间只有一个进程工作,一个进程工作完成后才会有之后的进程启动
  • 异步:三个进程同时并行工作
    image-20220520165010196
  • 3、代码实现
    image-20220520165038253
    image-20220520165045101
  • 注意:异步方式需要pool.close()表示不再接收新的任务,pool.join()优先执行!!!让主进程等着
  • 小结:
    image-20220521161931207
5.5.2 进程池中的Queue
  • 目标
    • 实现进程池中进程间通信
      image-20220520165133269
  • 上述中,需要用Multiprocessing.Manager.Queue()创建的才是进程池中的队列

  • 代码实现
    image-20220521163811964

  • 小结
    image-20220521163651256

5.6 文件夹copy器(多进程版)

使用进程实现文件夹整体拷贝到另外一个目录

  • 1、案例介绍
    image-20220521164535147

  • 2、实现步骤
    image-20220521164557671

  • 3、案例代码
    image-20220521164613669
    image-20220521164635565
    image-20220521164642194

  • 小结
    image-20220521173050849

  • !!子进程会拷贝主进程的资源!!

6 多任务-协程

迭代器->生成器->协程

6.1 可迭代对象,迭代器

6.1.1 可迭代对象及其检测方法
  • 目标
    • 了解可迭代对象
    • 使用instance()检测对象是否可迭代
  • 迭代是访问集合元素的一种方式,迭代器是一个可以记住遍历位置的对象;
  • 迭代器对象从集合的一个元素开始访问,直到所有的元素被访问完结束;
  • 迭代器只能往前不会后退
  • 1、可迭代对象
    image-20220521180952034

  • 测试的有:列表(list)、字符串(str)、字典(dictionary)、range()、
    image-20220521181010208

  • 将可以用于for…in…的对象称为可迭代对象

  • 2、判断对象是否可以迭代
    image-20220521181420683
    image-20220521181329195

  • 3、可迭代对象的本质
    image-20220521181518150

6.1.2 迭代器及其使用方法
  • 目标
    • 使用iter函数可以获得可迭代对象的迭代器;
    • 使用next函数可以获得迭代器数据;
  • 1、迭代器

    • 可迭代对象通过**__ iter __方法**向我们提供一个迭代器,
    • 在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,
    • 然后通过这个迭代器来一次获取对象中的每一个数据;
  • 2、iter()函数与next()函数

    • list\tuple等都是可迭代对象,我们可以通过Iter函数获取这些可迭代对象的迭代器;
    • 然后对获取到的迭代器不断使用next()函数获取下一条数据;
    • iter()函数实际上是调用了可迭代对象的__ iter __对象;
      image-20220521182321502
      image-20220521182348262
  • 小结
    image-20220521204716930

6.1.3 自定义迭代对象、迭代器
  • 目标
    • 自定义一个列表
      image-20220521210203439
  • 1、自定义迭代器Iterator
  • 一个类实现了__ iter __ 方法和 __ next __ 方法的对象,就是自定义迭代器;

  • 自定义迭代器类,必须满足如下:

    1)实现 __ iter __ ()方法;

    2)实现 __ next __ ()方法;

  • iter(迭代器对象),调用对象的 __ iter __ ()方法;

  • next(迭代器对象), 调用对象的 __ next __ ()方法;

  • 迭代器自身正是一个迭代器,所以迭代器的 __ iter __方法返回自身即可;

  • 2、代码实现
    image-20220521210701123
    image-20220521210711671
  • 3、for…in…循环本质
  • for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器, 然后对获取到的
    迭代器不断调⽤next()⽅法来获取下⼀个值并将其赋值给item, 当遇到StopIteration的异常后循环结
    束。
    image-20220521210802050
6.1.4 迭代器案例—斐波那契数列
  • 1、迭代器应用

    • 迭代器最核心功能:通过next()函数调用返回下一个数据值;

    • 如果每次返回的数据值不是在一个已有的数据集合中读取,而是程序计算生成,那么就不用将所有迭代数据都一次性缓存后供后续依次读取,这样可以节省大量存储(内存)空间。

  • 斐波那契数列
    image-20220523200930573

  • 2、代码实现

image-20220523201010319
image-20220523201019840

  • 3、不是只有for循环能接受可迭代对象

除了for循环能接收可迭代对象, list、 tuple等也能接收

image-20220523201108955

6.2 生成器

6.2.1 生成器-基本使用

使用两种方法创建生成器

  • 1、生成器
  • 生成器,可以每次迭代获取数据(通过next()方法)按照特定的规律生成;
  • 上节迭代器也可以实现,但是需要记录当前迭代状态,根据当前状态生成下一个数据;
  • 生成器是一类特殊的迭代器;
  • 2、创建生成器方法1—列表式
    image-20220523204643362

  • 创建 L 和 G 的区别仅在于最外层的 [ ] 和 ( ) , L 是⼀个列表, ⽽ G 是⼀个⽣成器。 我们可以直接打印
    出列表L的每⼀个元素, ⽽对于⽣成器G, 我们可以按照迭代器的使⽤⽅法来使⽤, 即可以通过next()
    函数、 for循环、 list()等⽅法使⽤。

  • 3、创建生成器方法2—函数中使用yield

    • generator⾮常强⼤。 如果推算的算法⽐较复杂, ⽤类似列表⽣成式的 for 循环⽆法实现的时候, 还可
      以⽤函数来实现。
    • image-20220523210338087
  • 使⽤了yield关键字的函数不再是函数, ⽽是⽣成器。 (使⽤了yield的函数就是⽣成器)

    • yield关键字有两点作⽤:
      • 保存当前运⾏状态(断点) , 然后暂停执⾏, 即将⽣成器(函数) 挂起;
      • 将yield关键字后⾯表达式的值作为返回值返回, 此时可以理解为起到了return的作⽤;
  • 程序流程分析

    • 第一次从def开始一直到yield,第二次开始从yield进入while循环,之后就一直在循环中

image-20220523211343542

  • 重点!!!:程序第一次Next会进入函数,然后进入while循环,到yield,直接停止到这里,保存程序的运行状态,只有在第二次开始调用next函数时,从yield暂停处开始执行,一直在while循环中,一直到再次暂停在yield位置;!!!

  • 小结
    image-20220523205925238

6.2.2 生成器-使用注意
  • 目标
    • 使用send()方法能够启动生成器、并传递参数;
    • 说出协程中return的作用;
  • 1、生成器中使用return问题
    image-20220524110938448

  • return会结束生成器!

  • next()唤醒生成器,继续从yield位置开始,

  • 现在send()也可以唤醒生成器,并且可以传递参数;

  • 2、使用send唤醒
    image-20220524111551655

注意:使用send启动生成器的时候传入的参数必须是None,下次启动生成器时候可以加上参数;

提示:一般第一次启动生成器使用next;
image-20220524112518850

  • 3、总结

    • 1)可使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数);
    • 2)Python3的生成器可以使用return返回最终执行的返回值,而Python2的生成器不允许使用;
    • 3)return返回一个返回值(既可以使用return从生成器中退出,但return后不能有任何表达式);
  • 小结
    image-20220524112621490

6.3 协程

6.3.1 协程—yield
  • 目标
    • 使用yield关键字可以实现协程
  • 1、协程概念

    • 基础概念
      • 协程,又称为微线程,纤程(Coroutine);
      • **“协程就是可以暂停执行的函数”,**可以理解为生成器;
    • 线程和进程的操作是由程序触发系统接口,执行者是系统;而协程的操作是程序员;
    • 协程存在的意义:在不切换线程的情况下,实现多任务
      • 对于多线程应用,CPU通过切片的方式切换线程间的执行,线程切换耗时(保存状态,下次继续执行);
      • 协程,则是使用一个线程(单线程),在一个线程中规定某一个代码块执行顺序;
      • image-20220524113326023
  • 协程适用场景:当程序中存在大量不需要CPU操作时(IO),适用协程;
    image-20220524113446236

  • 2、协程和线程差异

    • 实现多任务时,线程切换从系统层面远不止保存和恢复CPU上下文这么简单,操作系统为了程序运行的高效性,每个线程·都有自己的·缓存Cache等数据,操作系统还会帮你做数据的恢复操作;
    • 所以,线程的切换非常耗性能;但是协程的切换只是单纯的操作CPU的上下文,所以甚至可以做到一秒钟切换上百万次协程切换;
  • 3、简单实现协程
    image-20220524130818993

  • 小结
    image-20220524132425517

6.3.2 协程–greelet
  • 目标
    • 使用greenlet实现协程
      image-20220524132543106
      image-20220524133556792
  • 小结
    image-20220524134433285
6.3.3 协程—gevent
  • 使用gevent实现协程
    image-20220524134607337
  • 1、gevent的使用
    image-20220524134715462
  • 我们是希望 gevent 帮我们我们⾃动切换协程以达到work1 和 work2 交替执⾏的⽬的, 但并没有达到我们的效果;
    • 因为 time.sleep(0.2) 并没有被正确的识别到, 所以要使⽤下⾯的 gvent.sleep() 来实现延时(耗时) 操作
  • 2、gevent.sleep()
    image-20220524134822419
  • 如果我们以前的代码中⼤量使⽤了 time.sleep() 等耗时⽅法, 如果全部改为 gevent.sleep()
    • 为了让程序更好的兼容 time.sleep() 我们可以给程序打补丁, 以实现兼容
  • 3、给程序打补丁(猴子补丁)
    image-20220524134947056
  • 4、查看当前执行任务的协程
    image-20220524135048868
6.3.4 进程、线程、协程对比
  • 目标
    • 进程、线程、协程应用场景
  • 1、概念

    • 1)进程
      • 进程是具有一定独立功能的程序关于某一个数据集合上的一次运行活动;
      • 进程是系统进行资源分配和调度的一个独立单位;
      • 每个进程都自己独立内存空间,不同进程通过进程间通信来通信;
      • 进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全;
    • 2)线程
      • 线程是进程的一个实体,CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位;
      • **线程基本不拥有系统资源,**只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈);
      • 但它可与同属一个进程的其他线程共享进程中全部资源;
      • 线程间通信共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定,容易丢失数据;
    • 3)协程
      • 协程是一种用户态的轻量级线程,协程的调度完全由用户控制;
      • 协程拥有自己的寄存器上下文和栈;
      • 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内和切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快;
  • 2、进程、线程、协程的关系
    image-20220524141053555

  • 3、应用场景
    image-20220524141157129

  • 效率最高:进程+协程

  • 小结
    image-20220524142326744

6.4 案例—并发下载器

  • 1、代码实现
    image-20220524192444795
  • 2、打补丁,识别耗时操作
    image-20220524192529803
  • 3、并发下载视频
    image-20220524192549405

7 正则表达式

7.1 正则表达式基础

  • 1、思考
    image-20220524194502389

  • 2、正则表达式概述

    • 正则表达式,又称规则表达式,(Regular Expression,简称regex、regexp或RE);
    • 正则表达式通常被用来检索、替换哪些符合某个模式(规则)的文本。
      image-20220524194658336
  • 2、正则表达式用处

    • 1)测试字符串的某个模式,即数据有效性验证;
    • 2)按照某种规则替换文本;
    • 3)根据模式匹配从字符串中提取一个字符串(爬虫);
  • 3、正则表达式的构成

    • 1)原子(普通字符,如英文字符);
    • 2)元字符(有特殊功用的字符);
    • 3)以及模式修正字符组成;
  • 注意:一个正则表达式中至少包含一个原子;

  • 4、工具:RegexBuddy

7.2 正则表达式基本规则

7.2.1 匹配单/多个字符
  • 1、单个字符
    image-20220524201524891
  • 2、多个字符
    image-20220524201738383
    image-20220524205135797
  • 匹配出,变量名是否有效
    image-20220524205310475
  • 匹配出,0到99之间的数字
    image-20220524205402743
  • 不是以4、7结尾的手机号码(11位)
    image-20220524205806023
    image-20220524205811615
    image-20220524210035337
  • 匹配8到20位的密码,可以是大小写英文字母、数字、下划线
    image-20220524210146869
  • 匹配163邮箱地址,@符号之前有4到20位,例如hello@163.com
    image-20220524210344132
7.2.2 匹配开头结尾

image-20220524210632992

  • 匹配163.com邮箱地址
    image-20220524210740863
  • 注意,在[]外面,表示以开头,在[]表示不包含(取反)
    image-20220524211510563
7.2.3 re模块的操作
  • 目标
    • re.match方法的作用;
    • group方法的作用;
  • 在Python中需要通过正则表达式对字符串进行匹配时,还可以使用re模块
  • 1、re模块的使用过程
    image-20220525190456602

  • 之前的是正则表达式的一个测试工具;

  • re是python实现正则表达式的模块

  • 2、re模块实例(匹配以itcase开头的语句)
    image-20220525190532583

image-20220525190542600

  • 3、说明
    • re.match()能够匹配出itcase.cn字符串中开头部分的Itcast;
7.2.5 匹配分组“|”

image-20220525191822962
image-20220525191843688
image-20220525191849902

7.2.6 匹配分组“()”

image-20220525191913870
image-20220525191923319
image-20220525191928885
image-20220525193630691
image-20220525193951319

7.2.7 匹配分组“ \ "

image-20220525205739892

  • hh<\html>前后尖括号内容要求保持一致!!—引用—
  • "<(\w)><(\w)>.*</\2></\1>"**
    image-20220525210358564
    image-20220525191949952
    image-20220525192010729
    image-20220525211006029

7.3 re模块的高级用法

  • 目标
    • re模块的search方法作用;
    • re模块的sub方法作用;
    • re模块的split方法作用;
    • re模块findall方法作用;
  • 1、search,搜索匹配
    image-20220526170005639
    image-20220526165303165
  • 2、findall,查找所有,返回列表
    image-20220526165335957
  • 3、sub将匹配到的数据进行替换
    • sub(“正则表达式”,“新的内容”,“要替换的字符串”);
    • 返回值是替换后的字符串;
      image-20220526165401407

image-20220526165417192
image-20220526170809484

  • 4、split根据匹配进行切割字符串,并返回一个列表
    image-20220526165501942

7.4 使用注意

7.4.1 贪婪和非贪婪
  • 知道贪婪匹配的特点;
    image-20220526171437763
    image-20220526171450037
7.4.2 r的作用
  • 知道r的作用
    • Python中在正则化字符串前面加上’r’表示;
    • 让正则中的’'不再具有转义功能(默认为转义),就是表示原生字含义一个斜杠;
  • r的作用只是让正则中的\没有特殊含义(转义)就是代表原生的斜杠;比如如果是.就是不可以!
  • 1、原来的写法
    image-20220526184957148
  • 2、使用r的写法
    image-20220526185013959
    image-20220526185021293

7.5 案例—简单爬虫(批量获取电影下载链接)

  • 1、html代码分析

    • 列表页面url分析
    • 内容页下载链接分析
  • 2、正则分析

    • 分析列表中的电影的url地址
    • 分析内容页的下载地址
  • 3、代码实现
    image-20220526185840893
    image-20220526185849346

  • 小结
    image-20220526193729715

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值