多任务系统
多任务系统可以同时运行多个任务。
单核cpu也可以执行多任务,由于cpu执行代码都是顺序执行的,那么cpu是怎么执行多任务的?
答案是操作系统轮流让各个任务交替执行
任务1执行0.01s切换任务2,任务2执行0.01s切换任务3.
依次类推,表面上看,每个任务都是交替执行的,但是由于cpu执行速度实在太快,感觉上就是所有任务同时执行。
并发
并发 任务数多于cpu核数,通过操作系统的各种任务调度算法,实现多个任务一起执行,而实际上总有一些任务不在执行,因为切换速度够快,看上去一起执行
并行
并行 任务数量小于cpu核数,任务是真正的一起执行的
并发 10个客人点餐,1个服务员应对
并行 10个客人点餐,10个服务员应对
串行
先执行a,再执行b
同步与异步
同步
同步协调,指线程在访问某一资源类的时候,获得了该资源的返回结果之后才会执行其他操作,先做某件事,在做某件事,一步一步来
异步
异步 提交注册数据----校验数据----注册成功---登陆,校验校验数据之后即发送账号激活---邮件激活,两者同时发生,不用的等待注册总流程跑完再进行激活
python中的线程模块
threading
python的thread是较底层的模块,threading是对thread的一些封装。
通过target=指定开启线程的函数创建线程对象
调用threading.Thread(target=开启线程的目标函数) 返回为一个线程对象,
通过继承Thread类重写run方法来开启线程
编写一个类,之后继承threading.Thread 并重写run方法。
至于为什么重写,来看一段thread的原码
这其中的self._target是是thread的类变量,在thread类被初始化的时候由target传入,if判断如果被传入,即调用被传入的目标函数。
而在写自己的方法类时,对run方法进行重写之后,便不在需要传入参数target,
第二种方式创建的实例代码。
补充:如果想要对线程操作类实现参数化进行复杂操作。
编写init方法时声明参数,同时不能忘记继承并执行父类的init方法,父类thread在类初始化时做了相当多的配置达到多线程运行的目的,不写的话会报错
线程类常用方法
run() 用以表示线程活动的方法
start() 启动线程活动
join([time]) 设置主线程会等待time秒后再往下执行,time默认为子线程结束
多个子线程之间的设置会叠加
isAlive() 返回线程是否活动的
getName() 返回线程名
setName() 设置线程名
threading.currentThread() 返回当前执行的线程
threading.enumerate() 返回正在运行的所有线程(list),正在运行 指的是线程启动后,结束前,不包括启动前和终止之后的线程
threading.activateCount() 返回正在运行的线程数量。。
多线程的bug---数据安全
多线程中如果使用全局变量,数据会变的不安全不稳定
原因: 子线程和总线程使用的是同一块内存空间,在主线程中存在的变量为全局变量,各个线程都可以使用,
python只支持单核,通过多任务之间快速切换,任务量少的时候是完全没问题的。
但是在任务量级特别大的时候,线程之间的切换就会发生问题,数据产生偏差
从10W量级时开始出现问题。
下面看代码实例
互斥锁
为了解决这样的线程互相竞争资源导致数据不稳定不安全的问题,引入锁的概念。
线程同步能够邦正多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
互斥锁为资源引入一个状态,锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源状态为锁定,其他线程不能更改知道该线程释放资源。
将资源的状态改变成 非锁定 其他的线程才能再次锁定该资源进行操作。,
互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程下数据的正确性。
threading 模块中定义了Lock类,可以方便的处理锁定
上锁的过程
当一个线程调用锁的acquire(0方法获得锁时,锁进入locked状态
每次只有一个线程可以获得锁,如果此时另一个线程视图获得这个锁,该线程就会变成blocked被阻塞,知道拥有锁的线程调用锁的release()释放锁之后,锁进入unlocked状态。
线程调度程序从处于通同步阻塞状态的线程中选一个来获得锁,并使得该线程进入 running状态。
总结
锁的好处
确保了某段关键代码只能由一个线程从头到尾完整执行
锁的坏处
组织了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率大大下降,犹豫可以存在多个锁,不同线程持有不同的锁,并试图跟获取对象持有的锁时,可能会造成线程死锁,导致程序直接阻塞不向下运行
正案例
来看一个线程锁的应用实例。
死锁案例
该案例中,两边互相等待对方线程释放资源,就会造成无限等待,行程死锁。
全局解释器锁 GIL
官方文档中,搜索关键字 怎样修改全局变量是线程安全的?
不用自己操作跟定义, 控制线程运行的
描述 gil的概念,以及它对python多线程的影响
参考哦
1,python语言和GIL没有关系,仅仅是犹豫历史原因在Cpython虚拟机(解释器)难以移除GIL
2,GIL 全局解释器,每个线程在执行的过程都需要先获取GIL,保证同一时刻只有一个线程可以执行代码
3,线程释放GIL锁的情况,
---在io操作等可能会引起阻塞的 system call之前,可以暂时释放GIL,但在执行完毕后
必须重新获取 GIL
--python 3使用计时器,执行时间达到阈值之后 当前线程释放GIL 或python 2.x 技tickets技术达到100
4 python使用多进程是可以利用多核的cpu资源的
多线程与单线程分别更适合什么
1.io密集型:涉及到网络、磁盘io的任务都是io密集型任务,这类任务的特点是cpu消耗很少,任务的大部分的
时间都在等待io操作完成(因为io的速度远远低于cpu和内训的速度)
结论:io密集型操作,多线程比单线程要快
2.cpu密集型:cpu密集型也称为计算密集型,任务的特点是要进行大量的计算,消耗cpu资源,比如
计算圆周率、对视频进行高清解码等等,全靠cpu的运算能力
结论:cpu密集型操作,单线程比多线程要快
队列
python的queue模块中提供了同步的,线程安全的队列类,
1,FIFO 先进先出 queue
2,LIFO 后进先出队列 LifoQueue
3,PriorityQueue 按照优先级
能够在多线程中直接使用,
可以用队列来实现线程间的同步。
初始化queue() 对象时, q = queue()
如果参数中没有指定最大可接受消息量,货数量为负值
则代表可接受的消息没有上限