简单地说,就是同时可以运行多个任务。打个比方,你一边在用浏览器上网,一边在听MP3,一边在用Word赶作业,这就是多任务。
指的是任务数小于等于cpu核数,在一段时间内真正的同时一起执行多个任务。每个核同时处理不同的任务,即任务真的是同时执行的。
在一段时间内交替去执行多个任务,对于单核CPU处理多任务,操作系统轮流让各个任务在cpu上交替执行(多个任务看起来是同时运行的)
真正的"并行"只能在多核CPU上实现,现实中由于任务数量远远多于CPU的核心数量,所以基本上都是“并发”。 操作系统会自动把很多任务轮流调度到每个核心上执行。
多个任务时,运行完一个再运行下一个
线程是执行程序的最小单位
使用threading模块
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.如果在一个程序中需要有多个任务一起执行,可以将每个任务单独放到一个函数中
2.使用threading.Thread创建一个对象,注意实参target需要指定为刚刚定义的函数名(不要写上小括号,那表示调用函数了)
3.调用threading.Thread返回的对象中的start方法(会让这个线程开始运行)
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())
当python程序中有多个任务需要被执行时,这些任务需要等待操作系统的调度(即操作系统安排接下来要执行哪个任务),因为每次运行程序时的环境(例如上次运行时 除了这个python程序之外还有QQ、微信在运行,而这次运行时没有QQ只有微信在运行都会影响操作系统的调度策略)不一样,所以多次运行同一个python程序时任务执行的先后顺序是不同的
1.在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据
2.缺点是:线程是对全局变量随意遂改可能造成多线程之间对全局变量的混乱(即线程非安全)
为什么要用互斥锁?
当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制
线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。
互斥锁的作用
互斥锁为资源引入一个状态:锁定/非锁定
某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改;直到该线程释放资源,将资源的状态变成“非锁定”,其他的线程才能再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程情况下数据的正确性。
threading模块中定义了Lock类,可以方便的使用:
# 创建锁
mutex = threading.Lock()
# 锁定
mutex.acquire()
# 释放
mutex.release()
注意:
如果这个锁之前是没有上锁的,那么acquire不会堵塞(堵塞:理解为程序卡在这里等待某个条件满足)
如果在调用acquire对这个锁上锁之前 它已经被 其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止
锁的好处:
确保了某段关键代码同时只能由一个线程从头到尾完整地执行
锁的坏处:
阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁
进程(Process) 是资源分配的最小单位,它是操作系统进行资源分配和调度运行的基本单位,
通俗理解: 一个正在运行的程序就是一个进程。例如:正在运行的qq,微信等,他们都是一个进程。
注意:一个正在运行的程序才叫进程,而没有运行的程序,只能叫程序,不能叫进程。同时,一个程序可以有一个或者多个进程。
多进程的作用:
同时执行多个函数,提升效率
进程注意事项:
主进程会等待所有的子进程完成才结束
#(2)设置守护主进程:每一个子进程都守护主进程,当主进程结束了之后,子进程直接结束,也就是被销毁。
multiprocessing模块是跨平台版本的多进程模块,提供了一个Process类来创建一个进程对象,这个对象可以理解为是一个独立的进程,可以执行另外的事情
通过额外创建一个进程,可以实现多任务
使用进程实现多任务的流程:
创建一个Process对象,且在创建时通过target指定一个函数的引用
当调用start时,会真正的创建一个子进程
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)
进程间是相互独立的,数据不共享,但有时需要数据共享,就需要进程间通信(IPC)
可以使用multiprocessing模块的Queue实现多进程之间的数据传递
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())
协程,又称微线程
协程和线程差异
在实现多任务时, 线程切换从系统层面远不止保存和恢复 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()
1.线程是依附在进程里的,没有进程就没有线程
2.一个进程默认提供一条线程,进程可以创建多个线程
1.创建进程的资源开销比创建线程的资源开销要大
2.进程是操作系统资源分配的进本单位,线程是cpu调度的基本单位(程序执行的最小单位)
3.线程不能独立执行,必须依附在进程中
1.进程优点:可以用多核
缺点:资源开销大
2.线程优点:资源开销小
缺点:不能使用多核
无需跨进程边界;
程序逻辑和控制方式简单;
所有线程可以直接共享内存和变量等;
线程方式消耗的总资源比进程方式好;
每个线程与主程序共用地址空间,受限于2GB地址空间;
线程之间的同步和加锁控制比较麻烦;
一个线程的崩溃可能影响到整个程序的稳定性;
到达一定的线程数程度后,即使再增加CPU也无法提高性能,例如Windows
Server
2003,大约是1500个左右的线程数就快到极限了(线程堆栈设定为1M),如果设定线程堆栈为2M,还达不到1500个线程总数;
线程能够提高的总性能有限,而且线程多了之后,线程本身的调度也是一个麻烦事儿,需要消耗较多的CPU
每个进程互相独立,不影响主程序的稳定性,子进程崩溃没关系;
通过增加CPU,就可以容易扩充性能;
可以尽量减少线程加锁 / 解锁的影响,极大提高性能,就算是线程运行的模块算法效率低也没关系;
每个子进程都有2GB地址空间和相关资源,总体能够达到的性能上限非常大
逻辑控制复杂,需要和主程序交互;
需要跨进程边界,如果有大数据量传送,就不太好,适合小数据量传送、密集运算
多进程调度开销比较大;
最好是多进程和多线程结合,即根据实际的需要,每个CPU开启一个子进程,这个子进程开启多线程可以为若干同类型的数据进行处理。当然你也可以利用多线程 + 多CPU + 轮询方式来解决问题……
GIL 全局解释器锁
python有了GIL,为什么还有线程锁(互斥锁)?
GIL是限制同一个进程中只有一个线程进入Python解释器。。。。。
而线程锁是由于在线程进行数据操作时保证数据操作的安全性(同一个进程中线程之间可以共用信息,如果同时对数据进行操作,则会出现公共数据错误)
其实线程锁完全可以替代GIL,但是Python的后续功能模块都是加在GIL基础上的,所以无法更改或去掉GIL,这就是Python语言最大的bug…只能用多进程或协程改善,或者直接用其他语言写这部分