4 多任务—线程
4.1 多任务介绍
-
目标
-
1、多任务解析
- 操作系统可以同时运行多个任务,现在,多核CPU已经非常普及,但是,即使过去的单核CPU,也可以执行多任务。!!!通过时间片轮转的方式,单核多任务!!!
-
2、多任务表现形式
- window下打开任务管理可以很清晰看到多个进程同时执行任务,qq,微信都是以进程的形式寄存在window下,大多我们在协议一些控制台程序真正执行的时候都是以进程调度;
-
3、Python默认是单任务 * 小结
1、python默认为单任务;
2、多任务:同一时间多个任务同时执行;
4.1.2 多任务-线程
4.2 线程基础
4.2.1 线程-基本使用
-
目标
- 理解主线程和子线程关系;
- 使用threading.Thread类能够创建线程对象;
- threading.Thread的target参数能够指定线程执行的任务;
-
1、线程概念
- 线程,称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。
- 一个标准的线程由线程ID,当前指令指针PC,寄存器集合和堆栈组成。
- 线程是进程中的一个实体,是被系统独立调度和分派的基本单位。
- 线程自己不拥有系统资源,只拥有一点儿在运行中必不可少的资源,但它可与同属一个进程的其他线程共享进程所拥有的全部资源。
- 主线程默认就有,子线程需要自己用代码写出来才有
- 主线程:程序创建就会有一个主线程
? 当一个程序启动时,就有一个进程被操作系统(OS)创建,与此同时一个线程也立刻运行,该线程通常叫做程序的主线程。
主线程重要性:
1)是产生其他子线程的线程;
2)通常它必须最后完成比如执行各种关闭动作;
程序执行的一条分支,当子线程启动后会和主线程一起同时执行;
- 2、单线程执行
- 3、使用threading模块 创建子线程
- 线程对象只有调用了start()子线程才会执行!
- 子线程启动时,主线程一直都在执行!!
4.2.2 线程名称、总数量
-
目标
-
1、查看线程数量
- **
threading.enumerate() **获取当前所有活跃线程的列表,使用len()对列表长度可以看到目前活跃的线程 -
2、线程名称
threading.current_thread() 可以获取当前线程对象(含名称)
4.2.3 线程-线参数及顺序
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)
t1=threading.Thread(target=sing,args=(10,20,30))
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、线程的执行顺序
- 说明:
(1)多线程的执行顺序是不确定的;
(2)当执行到sleep语句时,线程将被阻塞(Blocked),到sleep结束时,线程进入到就绪状态,等待调度。而线程调度将自行选择一个线程执行。
(3)保证每个线程都运行完整,但是线程的启动顺序、run函数中每次循环的执行顺序都不能确定;
(1)每个线程默认与一个名字,尽管没有指定线程对象的name,但是python会自动为线程制定一个名字;
(2)当线程的run()方法结束时,该线程完成;
(3)无法控制线程调度程序,但可以通过别的方式影响线程调度的方式;
4.2.4 守护线程
-
目标
-
1、守护线程
- 如果在程序中将子线程设置为守护线程,则该子线程会在主线程结束时自动退出,设置方式为**
thread.setDaemon(True) ,要在thread.start() 之前设置,默认是false,就是主线程结束时,子线程依然在执行;** - 下列代码就是,主线程已经exit()【其实没有真正结束】,但是子线程还在继续执行;
-
2、设置守护线程
设置守护线程(如果主线程结束了,也随之结束)
线程.setDeamon(True)
4.3 并行和并发
- 目标
- 1、多任务原理剖析
- 2、并行和并发
- 并发:
- 任务数多于cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行;
- 真正的并行执行多任务只能在多核CPU上实现,由于任务数量远大于CPU的核心数量,因此,操作系统会自动把很多任务轮流调度到每个核心上执行。
- 并发:
- 任务数小于等于CPU核心,任务真的一起执行;
4.4 自定义线程类
-
目标
- 通过继承**
threading.Thread **可以实现自定义线程; -
1、线程执行代码的封装
-
说明:
(1)python的threading.Thread 类有一个run方法,用于定义线程的功能函数,可以在自己的线程类中覆盖该方法。
? 而创建自己的线程实例后,通过Tread类的start方法,可以启动该线程,交给python虚拟机进行调度,当该线程获得执行的机会时,会调用run方法执行线程;
(2)在run方法中,使用self.name 可以获取当前正在运行的线程名称;
4.5 多线程-全局变量
4.5.1 多线程-共享全部变量
-
目标
-
1、多个线程方法中可以共用全局变量 -
2、列表作为实参传递到线程中 -
小结
1)在一个进程内的所有线程共享全局变量,多个线程内共享数据;
2)缺点:线程是对全局变量随意篡改可能造成多线程之间对全局变量的混乱,即线程非安全
4.5.2 多线程-共享全局变量的问题
-
目标
-
1、多线程共享变量遇到的问题 -
2、产生原因 -
3、处理方式–join()让某一个线程优先处理
4.6 同步与锁
4.6.1 同步
-
1、同步与异步
- 同步:多任务
- 多个任务之间执行有先后顺序,必须一个执行后,另一个才可以继续执行,只有一个主线;
- 异步:
- 多个任务之间没有先后顺序,可以同时运行,存在多条运行主线,互不影响;
-
2、解决线程同时修改全局变量的方式 -
线程锁机制
4.6.2 互斥锁
-
1、互斥锁概念
- 当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。
- 互斥锁为资源引入状态:锁定/非锁定
- 某个线程要更改共享数据时,先将其锁定,此时的资源状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态改为“非锁定”,其他的线程才能再次锁定该资源,
- 互斥锁保证了每次只有一个线程写入操作,保证多线程数据的正确性;
-
threading模块定义了Lock类,方便处理锁定 -
2、互斥锁:100万次加操作 -
3、上锁解锁过程 -
第一个Printf的线程已经加完了自己的100,0000;剩下不到200,0000是另一个线程正在加; -
小结
4.6.3 死锁
- 1、死锁
- 在线程间共享多个资源的时候,如果两个线程分别占有一部分资源并且同时等待对方的资源,就会造成死锁;
- **尽管死锁很少发生,**一旦发生就会造成应用的停止响应;
- 2、解法办法
4.7 多任务版udp聊天器
4.13.1 同时收发消息
-
1、程序分析
-
说明
- 编写一个有2个线程的程序;
- 线程1:接收数据并显示;
- 线程2:检测键盘数据然后通过udp发送数据;
-
改进思路
- 1、单独分开子线程用于接收消息,以达到收发消息可以同时进行;
- 2、接收消息要能够连续接收多次;
- 3、设置子线程守护主线程;(解决无法正常退出的问题);
-
2、参考代码
4.13.2 支持接收多条信息+while True
4.8 TCP服务端框架
之前是一个用户连接后,一直发消息,一直到用户断开连接,才可以与其他用户建立连接,开始发消息;
1、实现指定端口监听;
2、实现服务器端地址重用,避免"Address already in use"错误;
3、支持多个客户端连接;
4、支持不同的客户端同时收发消息(开启子线程);
5、服务器端主动关闭服务后,子线程随之结束;
5 多任务-进程
5.1 进程基础
5.1.1 进程及其状态
-
1、进程概念
- 进程是资源分配的最小单元,是线程的容器,程序隔离的边界;
- !!!线程:是cpu调度的最小单位;
-
!!程序运行之后,会有一个进程=程序+资源!! -
2、进程的状态
工作中,任务数往往大于cpu的核数,即一定有一些任务正在执行,而另外一些任务在等待cpu进行执行,因此导致有了不同的状态;
5.1.2 进程的创建-multiprocessing
- 目标
- 知道使用Mutiprocessing.Process类能创建进程对象;
- 能够multiprocessing.Process的target参数能够指定进程执行的任务的函数
- !!!程序启动后有一个默认的主进程!!!在主进程之中才有主线程!!!
- !!!目前是在主进程中创建一个主线程!!!
5.1.3 进程名称、Pid
- 目标
- 使用getpid和getppid的获取进程id和进程父id
- 1、进程名称获取
- 2、进程pid
- 3、Kill-9 杀掉进程
- 小结
5.2 进程-参数传递、全局变量、守护进程
5.2.1 参数传递,全局变量
- 目标
- 多进程之间不能共享全局变量;
- 给子进程指定函数传递参数;
5.2.2 守护进程
- 1、守护主进程
p1.daemon=True 设置?进程 p1 守护主进程, 当主进程结束的时候, ?进程也随之结束p1.terminate() 终?进程执?, 并?是守护进程
5.3 进程、线程对比
-
1、功能
- 进程:完成多任务,一台电脑上同时运行多个QQ;
- 线程:完成多任务,一个QQ中多个聊天窗口;
-
2、使用区别
- 1)进程是系统进行资源分配和调度的一个独立单位;
- 2)线程是进程的一个实体,是cpu调度的基本单位,它是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他线程共享进程所拥有的全部资源;
- **3)**一个程序至少有一个进程,一个进程至少有一个线程;
- **4)**线程的划分尺度小于进程(资源比进程少),使得多线程程序的并发性性高;
- **5)**进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率;
-
- **6)**线程不能独立执行,必须依存于进程中;
- **7)**可以将进程理解为工厂的一条流水线,线程就是这个流水线上的工人;
-
3、进程与线程的选择取决于以下几点
-
注意:实际上基本上都是“进程+线程”的结合方式, -
小结
5.4 消息队列
5.4.1 基本操作
Process之间有时需要通信,操作系统提供了很多机制来实现进程间的通信;
- 1、Queue介绍
- 使用multiprocessing模块的Queue实现多进程之间的数据传递;
- Queue本身是一个消息队列程序
- 2、Queue基本使用
- put大于队列长度时,会一直等待,程序一直运行不会结束;
- 如果是选择put_nowait就会直接报错,不等待;
5.4.2 常见判断
- 目标
- 说出queue的full()和empty()的作用;
- 使用qsize()获取队列中的消息的个数;
5.4.2 常见判断
- 目标
- 说出queue的full()和empty()的作用;
- 使用qsize()获取队列中的消息的个数;
- 1、消息数量、判断是否为空、判断是否已满
- 消息数量qsize(),注意队列每get()一次,数量就会-1;
- empty()判断队列是否为空
!!!解决办法:time.sleep(0.001)稍微休眠一下;!!!
不然就会出错,保险起见,可以使用qsize()判断!!!
- full()判断队列是否已满,消息数量达到设定上限,则为满;
5.4.3 Queue实现进程间通信
- 1、进程间通信思路
- 2、Queue实现进程间通信
- 3、Queue使用过程中常见错误
- 1)qsize()报错
- 2)ImportError:cannot import name ‘Queue’
- 小结
5.5 进程池Pool
5.5.1 进程池基础
- 1、进程池概述—批量生成进程
- 2、核心方法
- 同步:同一时间只有一个进程工作,一个进程工作完成后才会有之后的进程启动
- 异步:三个进程同时并行工作
- 3、代码实现
- 注意:异步方式需要pool.close()表示不再接收新的任务,pool.join()优先执行!!!让主进程等着
- 小结:
5.5.2 进程池中的Queue
- 目标
- 实现进程池中进程间通信
5.6 文件夹copy器(多进程版)
使用进程实现文件夹整体拷贝到另外一个目录
-
1、案例介绍 -
2、实现步骤 -
3、案例代码 -
小结 -
!!子进程会拷贝主进程的资源!!
6 多任务-协程
迭代器->生成器->协程
6.1 可迭代对象,迭代器
6.1.1 可迭代对象及其检测方法
- 目标
- 了解可迭代对象
- 使用instance()检测对象是否可迭代
- 迭代是访问集合元素的一种方式,迭代器是一个可以记住遍历位置的对象;
- 迭代器对象从集合的一个元素开始访问,直到所有的元素被访问完结束;
- 迭代器只能往前不会后退
6.1.2 迭代器及其使用方法
- 目标
- 使用iter函数可以获得可迭代对象的迭代器;
- 使用next函数可以获得迭代器数据;
-
1、迭代器
- 可迭代对象通过**__ iter __方法**向我们提供一个迭代器,
- 在迭代一个可迭代对象的时候,实际上就是先获取该对象提供的一个迭代器,
- 然后通过这个迭代器来一次获取对象中的每一个数据;
-
2、iter()函数与next()函数
- list\tuple等都是可迭代对象,我们可以通过Iter函数获取这些可迭代对象的迭代器;
- 然后对获取到的迭代器不断使用next()函数获取下一条数据;
- iter()函数实际上是调用了可迭代对象的__ iter __对象;
-
小结
6.1.3 自定义迭代对象、迭代器
- 目标
- 自定义一个列表
-
一个类实现了__ iter __ 方法和 __ next __ 方法的对象,就是自定义迭代器; -
自定义迭代器类,必须满足如下:
1)实现 __ iter __ ()方法;
2)实现 __ next __ ()方法;
-
iter(迭代器对象),调用对象的 __ iter __ ()方法; -
next(迭代器对象), 调用对象的 __ next __ ()方法; -
迭代器自身正是一个迭代器,所以迭代器的 __ iter __方法返回自身即可;
- 2、代码实现
- 3、for…in…循环本质
- for item in Iterable 循环的本质就是先通过iter()函数获取可迭代对象Iterable的迭代器, 然后对获取到的
迭代器不断调?next()?法来获取下?个值并将其赋值给item, 当遇到StopIteration的异常后循环结 束。
6.1.4 迭代器案例—斐波那契数列
-
1、迭代器应用
-
斐波那契数列 -
2、代码实现
除了for循环能接收可迭代对象, list、 tuple等也能接收
6.2 生成器
6.2.1 生成器-基本使用
使用两种方法创建生成器
- 生成器,可以每次迭代获取数据(通过next()方法)按照特定的规律生成;
- 上节迭代器也可以实现,但是需要记录当前迭代状态,根据当前状态生成下一个数据;
- 生成器是一类特殊的迭代器;
6.2.2 生成器-使用注意
- 目标
- 使用send()方法能够启动生成器、并传递参数;
- 说出协程中return的作用;
注意:使用send启动生成器的时候传入的参数必须是None,下次启动生成器时候可以加上参数;
提示:一般第一次启动生成器使用next;
-
3、总结
- 1)可使用next()函数让生成器从断点处继续执行,即唤醒生成器(函数);
- 2)Python3的生成器可以使用return返回最终执行的返回值,而Python2的生成器不允许使用;
- 3)return返回一个返回值(既可以使用return从生成器中退出,但return后不能有任何表达式);
-
小结
6.3 协程
6.3.1 协程—yield
6.3.2 协程–greelet
- 目标
- 使用greenlet实现协程
- 小结
6.3.3 协程—gevent
- 使用gevent实现协程
- 1、gevent的使用
- 我们是希望 gevent 帮我们我们?动切换协程以达到work1 和 work2 交替执?的?的, 但并没有达到我们的效果;
- 因为 time.sleep(0.2) 并没有被正确的识别到, 所以要使?下?的 gvent.sleep() 来实现延时(耗时) 操作
- 2、gevent.sleep()
- 如果我们以前的代码中?量使?了 time.sleep() 等耗时?法, 如果全部改为 gevent.sleep()
- 为了让程序更好的兼容 time.sleep() 我们可以给程序打补丁, 以实现兼容
- 3、给程序打补丁(猴子补丁)
- 4、查看当前执行任务的协程
6.3.4 进程、线程、协程对比
-
1、概念
- 1)进程
- 进程是具有一定独立功能的程序关于某一个数据集合上的一次运行活动;
- 进程是系统进行资源分配和调度的一个独立单位;
- 每个进程都自己独立内存空间,不同进程通过进程间通信来通信;
- 进程比较重量,占据独立的内存,所以上下文进程间的切换开销(栈、寄存器、虚拟内存、文件句柄等)比较大,但相对比较稳定安全;
- 2)线程
- 线程是进程的一个实体,CPU调度和分派的基本单位,是比进程更小的能独立运行的基本单位;
- **线程基本不拥有系统资源,**只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈);
- 但它可与同属一个进程的其他线程共享进程中全部资源;
- 线程间通信共享内存,上下文切换很快,资源开销较少,但相比进程不够稳定,容易丢失数据;
- 3)协程
- 协程是一种用户态的轻量级线程,协程的调度完全由用户控制;
- 协程拥有自己的寄存器上下文和栈;
- 协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈,直接操作栈则基本没有内和切换的开销,可以不加锁的访问全局变量,所以上下文的切换非常快;
-
2、进程、线程、协程的关系 -
3、应用场景 -
效率最高:进程+协程 -
小结
6.4 案例—并发下载器
- 1、代码实现
- 2、打补丁,识别耗时操作
- 3、并发下载视频
7 正则表达式
7.1 正则表达式基础
-
1、思考 -
2、正则表达式概述
- 正则表达式,又称规则表达式,(Regular Expression,简称regex、regexp或RE);
- 正则表达式通常被用来检索、替换哪些符合某个模式(规则)的文本。
-
2、正则表达式用处
- 1)测试字符串的某个模式,即数据有效性验证;
- 2)按照某种规则替换文本;
- 3)根据模式匹配从字符串中提取一个字符串(爬虫);
-
3、正则表达式的构成
- 1)原子(普通字符,如英文字符);
- 2)元字符(有特殊功用的字符);
- 3)以及模式修正字符组成;
-
注意:一个正则表达式中至少包含一个原子; -
4、工具:RegexBuddy
7.2 正则表达式基本规则
7.2.1 匹配单/多个字符
- 1、单个字符
- 2、多个字符
- 匹配出,变量名是否有效
- 匹配出,0到99之间的数字
- 不是以4、7结尾的手机号码(11位)
- 匹配8到20位的密码,可以是大小写英文字母、数字、下划线
- 匹配163邮箱地址,@符号之前有4到20位,例如hello@163.com
7.2.2 匹配开头结尾
- 匹配163.com邮箱地址
- 注意,在[]外面,表示以开头,在[]表示不包含(取反)
7.2.3 re模块的操作
- 目标
- re.match方法的作用;
- group方法的作用;
- 在Python中需要通过正则表达式对字符串进行匹配时,还可以使用re模块
-
1、re模块的使用过程 -
之前的是正则表达式的一个测试工具; -
re是python实现正则表达式的模块 -
2、re模块实例(匹配以itcase开头的语句)
- 3、说明
- re.match()能够匹配出itcase.cn字符串中开头部分的Itcast;
7.2.5 匹配分组“|”
7.2.6 匹配分组“()”
7.2.7 匹配分组“ \ "
- hh<\html>前后尖括号内容要求保持一致!!—引用—
- "<(\w)><(\w)>.*</\2></\1>"**
7.3 re模块的高级用法
- 目标
- re模块的search方法作用;
- re模块的sub方法作用;
- re模块的split方法作用;
- re模块findall方法作用;
- 1、search,搜索匹配
- 2、findall,查找所有,返回列表
- 3、sub将匹配到的数据进行替换
- sub(“正则表达式”,“新的内容”,“要替换的字符串”);
- 返回值是替换后的字符串;
- 4、split根据匹配进行切割字符串,并返回一个列表
7.4 使用注意
7.4.1 贪婪和非贪婪
- 知道贪婪匹配的特点;
7.4.2 r的作用
- 知道r的作用
- Python中在正则化字符串前面加上’r’表示;
- 让正则中的’'不再具有转义功能(默认为转义),就是表示原生字含义一个斜杠;
- r的作用只是让正则中的\没有特殊含义(转义)就是代表原生的斜杠;比如如果是.就是不可以!
- 1、原来的写法
- 2、使用r的写法
7.5 案例—简单爬虫(批量获取电影下载链接)
-
1、html代码分析
-
2、正则分析
-
3、代码实现 -
小结
|