1.多任务
简单地说,就是同时可以运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务。
2.并行
指的是任务数小于等于cpu核数,在一段时间内真正的同时一起执行多个任务。每个核同时处理不同的任务,即任务真的是同时执行的。文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
3.并发
在一段时间内交替去执行多个任务,对于单核CPU处理多任务,操作系统轮流让各个任务在cpu上交替执行(多个任务看起来是同时运行的)
真正的"并行"只能在多核CPU上实现,现实中由于任务数量远远多于CPU的核心数量,所以基本上都是“并发”。 操作系统会自动把很多任务轮流调度到每个核心上执行。文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
4.串行
多个任务时,运行完一个再运行下一个文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
python实现多任务的方法
1.多线程
线程是执行程序的最小单位
使用threading模块文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
import threading import time def say_sorry(): print("亲爱的,我错了,我能吃饭了吗?") time.sleep(1) for i in range(5): t = threading.Thread(target=say_sorry) t.start() # 启动线程,即让线程开始执行
1.1同时执行多个不同的任务:
1.如果在一个程序中需要有多个任务一起执行,可以将每个任务单独放到一个函数中
2.使用threading.Thread创建一个对象,注意实参target需要指定为刚刚定义的函数名(不要写上小括号,那表示调用函数了)
3.调用threading.Thread返回的对象中的start方法(会让这个线程开始运行)文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
import threading from time import sleep, ctime def sing(): for i in range(3): print("正在唱歌...%d" % i) sleep(1) def dance(): for i in range(3): print("正在跳舞...%d" % i) sleep(1) print('---开始---:%s' % ctime()) t1 = threading.Thread(target=sing) t2 = threading.Thread(target=dance) t1.start() t2.start() #sleep(5) # 屏蔽此行代码,试试看,程序是否会立马结束? print('---结束---:%s' % ctime())
1.2多线程执行的顺序不确定
当python程序中有多个任务需要被执行时,这些任务需要等待操作系统的调度(即操作系统安排接下来要执行哪个任务),因为每次运行程序时的环境(例如上次运行时 除了这个python程序之外还有QQ、微信在运行,而这次运行时没有QQ只有微信在运行都会影响操作系统的调度策略)不一样,所以多次运行同一个python程序时任务执行的先后顺序是不同的文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
1.3多线程-共享全局变量
1.在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据
2.缺点是:线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
1.4互斥锁
为什么要用互斥锁?
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
互斥锁的作用
互斥锁为资源引入一个状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
threading模块中定义了Lock类,可以方便的使用:文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
# 创建锁 mutex = threading.Lock() # 锁定 mutex.acquire() # 释放 mutex.release()
注意:
如果这个锁之前是没有上锁的,那么acquire不会堵塞(堵塞:理解为程序卡在这里等待某个条件满足)
如果在调用acquire对这个锁上锁之前 它已经被 其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
锁的好处:
确保了某段关键代码同时只能由一个线程从头到尾完整地执行文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
锁的坏处:
阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
2.多进程
进程(Process) 是资源分配的最小单位,它是操作系统进行资源分配和调度运行的基本单位,
通俗理解: 一个正在运行的程序就是一个进程。例如:正在运行的qq,微信等,他们都是一个进程。
注意:一个正在运行的程序才叫进程,而没有运行的程序,只能叫程序,不能叫进程。同时,一个程序可以有一个或者多个进程。文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
多进程的作用:
同时执行多个函数,提升效率文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
进程注意事项:
主进程会等待所有的子进程完成才结束
#(2)设置守护主进程:每一个子进程都守护主进程,当主进程结束了之后,子进程直接结束,也就是被销毁。文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
2.1python多进程的实现
multiprocessing模块是跨平台版本的多进程模块,提供了一个Process类来创建一个进程对象,这个对象可以理解为是一个独立的进程,可以执行另外的事情文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
通过额外创建一个进程,可以实现多任务
使用进程实现多任务的流程:
创建一个Process对象,且在创建时通过target指定一个函数的引用
当调用start时,会真正的创建一个子进程文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
from multiprocessing import Process import time def test(): """子进程单独执行的代码""" while True: print('---test---') time.sleep(1) if __name__ == '__main__': p=Process(target=test) p.start() # 主进程单独执行的代码 while True: print('---main---') time.sleep(1)
2.2进程不共享全局变量
进程间是相互独立的,数据不共享,但有时需要数据共享,就需要进程间通信(IPC)文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
2.3进程间通信-Queue
可以使用multiprocessing模块的Queue实现多进程之间的数据传递文章源自爱尚资源教程网-https://www.23jcw.net/6352.html
from multiprocessing import Queue q = Queue(3) # 初始化一个Queue对象,最多可接收三条put消息 q.put("消息1") q.put("消息2") print(q.full()) # False q.put("消息3") print(q.full()) # True # 因为消息列队已满,所以会导致下面的try都会抛出异常, # 第一个try会等待2秒后再抛出异常 # 第二个Try会立刻抛出异常 try: q.put("消息4", True, 2) except: print("消息列队已满,现有消息数量:%s" % q.qsize()) try: q.put_nowait("消息4") except: print("消息列队已满,现有消息数量:%s" % q.qsize()) # 推荐的方式,先判断消息列队是否已满,再写入 if not q.full(): q.put_nowait("消息4") # 读取消息时,先判断消息列队是否为空,再读取 if not q.empty(): for i in range(q.qsize()): print(q.get_nowait())
3.协程
协程,又称微线程
协程和线程差异
在实现多任务时, 线程切换从系统层面远不止保存和恢复 CPU上下文这么简单。 操作系统为了程序运行的高效性每个线程都有自己缓存Cache等等数据,操作系统还会帮你做这些数据的恢复操作。 所以线程的切换非常耗性能。但是协程的切换只是单纯的操作CPU的上下文,所以一秒钟切换个上百万次系统都抗的住。
简单实现协程
import time def work1(): while True: print("----work1---") yield time.sleep(0.5) def work2(): while True: print("----work2---") yield time.sleep(0.5) def main(): w1 = work1() w2 = work2() while True: next(w1) next(w2) if __name__ == "__main__": main()
4.线程、进程对比
4.1关系对比
1.线程是依附在进程里的,没有进程就没有线程
2.一个进程默认提供一条线程,进程可以创建多个线程
4.2区别对比
1.创建进程的资源开销比创建线程的资源开销要大
2.进程是操作系统资源分配的进本单位,线程是cpu调度的基本单位(程序执行的最小单位)
3.线程不能独立执行,必须依附在进程中
4.3优缺点
1.进程优点:可以用多核
缺点:资源开销大
2.线程优点:资源开销小
缺点:不能使用多核
4.4多线程的优点:
无需跨进程边界;
程序逻辑和控制方式简单;
所有线程可以直接共享内存和变量等;
线程方式消耗的总资源比进程方式好;
4.5多线程缺点:
每个线程与主程序共用地址空间,受限于2GB地址空间;
线程之间的同步和加锁控制比较麻烦;
一个线程的崩溃可能影响到整个程序的稳定性;
到达一定的线程数程度后,即使再增加CPU也无法提高性能,例如Windows
Server
2003,大约是1500个左右的线程数就快到极限了(线程堆栈设定为1M),如果设定线程堆栈为2M,还达不到1500个线程总数;
线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU
4.6 多进程优点:
每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;
通过增加CPU,就可以容易扩充性能;
可以尽量减少线程加锁 / 解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系;
每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大
4.7多线程缺点:
逻辑控制复杂,需要和主程序交互;
需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算
多进程调度开销比较大;
最好是多进程和多线程结合,即根据实际的需要,每个CPU开启一个子进程,这个子进程开启多线程可以为若干同类型的数据进行处理。当然你也可以利用多线程 + 多CPU + 轮询方式来解决问题……
补充
GIL 全局解释器锁
python有了GIL,为什么还有线程锁(互斥锁)?
GIL是限制同一个进程中只有一个线程进入Python解释器。。。。。
而线程锁是由于在线程进行数据操作时保证数据操作的安全性(同一个进程中线程之间可以共用信息,如果同时对数据进行操作,则会出现公共数据错误)
其实线程锁完全可以替代GIL,但是Python的后续功能模块都是加在GIL基础上的,所以无法更改或去掉GIL,这就是Python语言最大的bug…只能用多进程或协程改善,或者直接用其他语言写这部分