-
多任务的概念:就是操作系统可以同时运行多个任务
-
单核CPU可不可以执行多任务:
- 也可以执行多任务:由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
答案就是操作系统轮流让各个任务交替执行,任务1执行0.01秒,切换到任务2,任务2执行0.01秒,
再切换到任务3,执行0.01秒……这样反复执行下去。表面上看,每个任务都是交替执行的,但是,
由于CPU的执行速度实在是太快了,我们感觉就像所有任务都在同时执行一样 - 真正的并行执行多任务只能在多核CPU上实现,但是,由于任务数量远远多于CPU的核心数量,
所以,操作系统也会自动把很多任务轮流调度到每个核心上执行
- 也可以执行多任务:由于CPU执行代码都是顺序执行的,那么,单核CPU是怎么执行多任务的呢?
-
什么是并发和并行
- 并发:指的是任务数多余cpu核数,通过操作系统的各种任务调度算法,实现用多个任务“一起”执行(实际上总有一些任务不在执行,因为切换任务的速度相当快,看上去一起执行而已),并发是包含并行的
- 并行:指的是在同一时刻去执行,任务数小于等于cpu核数,即任务真的是一起执行
使用python实现同时执行多个任务(并发:进程、线程、协程都可以实现)
并发能做什么呢,一个简单的例子,假如一个人要同时完成两件事情,需要多久?
用代码实现如下:
import time
def func1():
for i in range(5):
print("------正在做事情1------")
time.sleep(1)
def func2():
for i in range(6):
print("------正在做事情2------")
time.sleep(1)
st = time.time()
func1()
func2()
et = time.time()
print("一共需要完成的时间",et - st)
# ======》 一共需要11秒
# 先做事情1
# 在做事情2
假如2个人要同时完成两件事情,需要多久,这就需要用到线程模块,多线程一起执行。
一 、线程模块的详细使用
-
threading模块介绍:
python的thread模块是比较底层的模块,python的threading模块是对thread做了一些包装的,可以更加方便的被使用
快速使用:from threading import Thread ,使用的是Thread类
-
创建线程对象: threading.Thread(target=任务函数)
其中参数target指定线程执行的任务 (函数)
-
线程参数的使用
Thread类有以下参数:
1、target参数: 指定任务函数
import time from threading import Thread def func1(): for i in range(5): print("-正在做事情1------") time.sleep(1) # 创建一个线程对象 aa = Thread(target=func1)2、name参数: 设置线程名
# 创建一个线程对象 aa = Thread(target=func1,name="henry")
3.、args参数: 给任务函数传参(参数传递的是元祖类型)
def func1(aa): for i in range(5): print("---{}---正在做事情1------".format(aa) time.sleep(1) # 创建一个线程对象 aa = Thread(target=func1,args=('张三',))4、kwargs参数:给任务函数传参(参数传递的是字典类型)
def func1(aa): for i in range(5): print("---{}---正在做事情1------".format(aa) time.sleep(1) # 创建一个线程对象 aa = Thread(target=func1,kwargs={'aa': "李四"})5、daemon参数:设置是否作为守护线程(bool值)
- 设置子线程守护主线程执行(主线程结束,守护线程自动结束)
- 如果没有设置守护线程,主线程跑完,子线程没有跑完,还会继续跑下去,不会有影响
- 如果设置了守护线程,主线程跑完,子线程没有跑完,当前线程全部结束(包括子线程,不管是否跑完)
- 如果多个子线程,一个设置了守护线程,一个没有设置守护线程,主线程结束,子线程还会继续跑下去,不会有影响
def func1(aa): for i in range(5): print("---{}---正在做事情1------".format(aa)) # print("---正在做事情1------") time.sleep(1) def func2(aa): for i in range(6): print("----{}--正在做事情2------".format(aa)) time.sleep(1) # 创建一个线程对象 t1 = Thread(target=func1, args=('张三',),daemon=True) # 设置守护线程 t2 = Thread(target=func2, kwargs={'aa': "李四"},daemon=True) # 设置守护线程 # 启动线程执行 t1.start() t2.start() time.sleep(2) print("----主线程执行结束-----------") -
线程方法的使用
Thread类提供了以下方法:
1、start()方法: 启动线程执行
# 启动线程执行 t1.start() t2.start()
2、join([time]): 设置主线程等待的时间
- 设置主线程会等待time秒后再往下执行,不设置time,则默认为子线程结束,多个子线程之设置的值会叠加
- 多个子线程同时使用join设置的时间会叠加
def func2(): for i in range(6): print("------正在做事情2------") time.sleep(1) # 创建一个线程对象 t1 = Thread(target=func2) # 子线程t1 # # 子线程t1启动执行 t1.start() # 主线程等待时间 t1.join() # 让主线程等待t1执行完,再往下执行 # 主线程等待子线程执行完 for i in range(10): print("主线程全部执行完成——————————————————————————")3、run方法 :设置线程执行的逻辑代码
# 如何实现线程类的实现实现多线程 1. 创建一个类需要继承 Thread类 2. 设置执行函数(run方法) 3. 设置线程参数 4. 启动线程 # 继承类来创建线程 class GetDataThread(Thread): # 继承Thread类 def run(self): """线程执行的逻辑代码""" for i in range(5): time.sleep(1) print("---{}--data-----{}".format(self.name, i)) # 多线程 t1 = GetDataThread(name='线程1') t2 = GetDataThread(name='线程2') # 启动线程 t1.start() t2.start()4、isDaemon: 判断是否为守护线程
5、isAlive: 判断线程支持存活(处于执行状态)
6、is_alive:判断线程支持存活(处于执行状态)
7、getName:获取线程名
print("线程名为:",t1.getName()) print("是否为守护线程:",t1.isDaemon()) print("线程是否处于执行状态:",t1.isAlive()) print("线程是否处于执行状态:",t1.is_alive()) -
多线程共享全局变量的问题
1、什么是多线程-共享全局变量
- 线程之间是共用同一块内存的,那么线程可以共享全局变量
- 在一个进程内的所有线程共享全局变量,很方便在多个线程间共享数据
2、多线资源竞争的问题
- 如果多个线程操作同一个全局资源,在频繁的切换线程会出现资源竞争问题,而导致数据丢失或者被覆盖
3、在多线程的时候,什么情况下会触发切换线程:
- 执行时间达到指定的阈值:0.005,(python中默认为0.005)
- 线程执行遇到IO(input,output)输入输出操作:如网络IO,文件IO,等待(time.sleep)
- 获取python线程切换的阈值: print(sys.getswitchinterval())
例如:在下面的函数中,a为全局变量,两个函数中都有,两个线程都去执行,导致a的值不正确
from threading import Thread a = 0 # 全局变量 def work1(): global a for i in range(500000): a += 1 print("work1执行完,a的值:", a) def work2(): global a for i in range(500000): a += 1 print("work2执行完,a的值:", a) t1 = Thread(target=work1) t2 = Thread(target=work2) t1.start() t2.start() # 执行结果 work执行完,a的值: 500000 work2执行完,a的值: 824157 a: 824157 -
多线程共享全局资源竞争如何解决
1、多线程操作时的解决思路:
- 线程同步能够保证多个线程安全访问竞争资源,最简单的同步机制是引⼊互斥锁
- 互斥锁为资源引⼊⼀个状态:锁定/⾮锁定
- 某个线程要更改共享数据时,先将其锁定,此时资源的状态为“锁定”,其他线程不能更改直到该线程释放资源,将资源的状态变成“⾮锁定”,其他的线程才能再次锁定该资源
- 互斥锁为资源引⼊⼀个状态:锁定/⾮锁定
- 斥锁保证了每次只有⼀个线程进⾏写⼊操作,从⽽保证了多线程情况下数据的正确性
- threading模块中定义了Lock类,可以方便的处理锁定
2、如何解决:
- 关键性的代码进行加锁,控制线程的执行,避免同时获取数据,对于全局数据修改的那行关键性的代码使用锁锁起来,防止执行到这行代码的时候进行线程切换
- 使用队列来存储数据(队列在设置和获取数据时都实现了锁的功效)
3、Threading模块中定义了Lock类,可以方便的处理锁定:
- 创建锁之前需要实例化: 变量 = threading.Lock()
- 锁定方法 - acquire(): 变量.acquire()
- 释放锁,待锁定 - release(): 变量.release()
4、注意点:
- 如果这个锁之前是没有上锁的,那么acquire不会堵塞
- 如果在调用acquire对这个锁上锁之前 它已经被 其他线程上了锁,那么此时acquire会堵塞,直到这个锁被解锁为止
5、锁的好处:
- 保了某段关键代码只能由一个线程从头到尾完整地执行
6、锁的坏处:
- 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地下降了
- 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁
例如:还是这个函数,a为全局变量,两个函数中都有,两个线程都去执行,去给两个函数分别加上锁,在执行看:
from threading import Thread,Lock a = 0 # 全局变量 # 实例化一个锁对象 lockA = Lock() def work1(): global a for i in range(500000): lockA.acquire() # 锁定 a += 1 lockA.release() # 释放锁定 print("work1执行完,a的值:", a) def work2(): global a for i in range(500000): lockA.acquire() # 锁定 a += 1 lockA.release() # 释放锁定 print("work2执行完,a的值:", a) t1 = Thread(target=work1) t2 = Thread(target=work2) t1.start() t2.start() t1.join() t2.join() # 执行结果 work执行完,a的值: 867609 work2执行完,a的值: 1000000 a: 1000000



