多任务
-
多任务含义:
生活:一边听歌,一边跳舞
电脑:同时运行多个程序,如:qq,微信,陌陌,浏览器
-
并发和并行
并发:任务数大于核心数,通过操作系统调度算法实现多个任务“同时”执行,实际上通过快速切换任务,看上去是一起执行的,时间片轮转方式
并行:任务数小于核心数,任务是真正一起执行
-
进程:正在运行的一个程序我们可以说是一个进程 ,是系统进行资源分配和调用的独立单元,每
一个进程都有自己独立的内存空间和系统资源
程序:运行的应用程序称之为进程。当一个程序不运行的时候我们称之为程序,当程序运行起来就是一个进程。通俗的来说不运行的时候就是程序,运行起来就是进程。程序只有一个,但是进程可以有多个
-
线程:线程是进程中的一条执行线路或者流程,程序执行的最小单位,线程是任务调度的最小单 位。
- 由于进程是资源拥有者,创建、撤消与切换存在较大的内存开销,因此需要引入轻型进程 即线程,进程是资源分配的最小单位,线程是 CPU 调度的最小单位(程序真正执行的时候调 用的是线程)每一个进程中至少有一个线程
进程和线程的关系:一个进程中可以有一到多个线程。一个线程只属于一个进程。一个进程中的多个线程是一种 竞争关系
创建线程
import time
import threading
#一边听歌,一边下载歌曲
def listen():
for i in range(1,6):
print("正在听歌")
time.sleep(1)
def down_load():
for i in range(1,6):
print("正在下载歌曲")
time.sleep(1)
# down_load()
# listen()
if __name__ == '__main__':
#创建多线程
t1 = threading.Thread(target=down_load)
t2 = threading.Thread(target=listen)
#开启线程
t1.start()
t2.start()
#执行顺序:程序运行时,代码从上向下走,走了if __name__ == '__main__':创建了两条线程,我们称之为子线程,程序运行时的线程我们称之为主线程
#子线程根据target=xxx,开始执行指定的函数
-
线程注意点
线程合适开启,何时结束 1.子线程何时开启,何时运行 当调用start()时,开启线程,再运行线程中的代码 2.子线程何时结束 子线程把target指向的函数中的语句执行完毕,当前子线程结束 3.主线程何时结束 所有子线程执行完毕后,主线程才结束 4.查看线程数量:threading.enumerate(),可以列出当前运行的所有线程
-
查看线程执行情况
import threading import time def test1(): for i in range(1,6): time.sleep(1) print("--子线程1--%d"%i) print("子线程1中查看线程情况",threading.enumerate()) def test2(): for i in range(1,6): time.sleep(1) print("--子线程2--%d"%i) print("子线程2中查看线程情况",threading.enumerate()) def main(): print("创建线程之前的线程情况",threading.enumerate()) #创建线程 t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) time.sleep(0.1) print("创建线程之后的线程情况", threading.enumerate()) #开启线程 t1.start() t2.start() time.sleep(1) print("调用start之后的线程情况", threading.enumerate()) time.sleep(6) print("等待子线程执行结束后的线程情况", threading.enumerate()) if __name__ == '__main__': main()
-
args传递参数
- 给函数传递参数,使用线程关键字args = ()进行传递参数,传递的参数是一个元组
import time import threading #一边听歌,一边下载歌曲 def listen(num): for i in range(1,num): print("正在听歌%d"%i) time.sleep(1) def down_load(num): for i in range(1,num): print("正在下载歌曲%d"%i) time.sleep(1) if __name__ == '__main__': #创建多线程 t1 = threading.Thread(target=down_load,args=(10,)) t2 = threading.Thread(target=listen,args=(10,)) #开启线程 t1.start() t2.start()
-
join方法
- 功能:当前线程执行完后其他线程才会继续执行
def main(): #创建多线程 t1 = threading.Thread(target=down_load,args=(5,)) t2 = threading.Thread(target=listen,args=(5,)) #开启线程 t1.start() t1.join() #t1执行完之后,t2和主线程才会执行结束 t2.start() # t2.join() #在t1,t2执行完后,再继续执行主线程 if __name__ == '__main__': main() print("程序执行结束了")
setDaemon() 方法
- setDaemon()将当前线程设置成守护线程来守护主线程:
-当主线程结束后,守护线程也就结束,不管是否执行完成 ,即主线程结束后不等待子线程,立即结束
-应用场景:qq 多个聊天窗口,就是守护线程
注意:需要在子线程开启的时候设置成守护线程,否则无效
import time
import threading
#一边听歌,一边下载歌曲
def listen(num):
for i in range(1,num):
print("正在听歌%d"%i)
time.sleep(1)
def down_load(num):
for i in range(1,num):
print("正在下载歌曲%d"%i)
time.sleep(0.5)
def main():
#创建多线程
t1 = threading.Thread(target=down_load,args=(5,))
t2 = threading.Thread(target=listen,args=(5,))
# t1.setDaemon(True)
t2.setDaemon(True)
#开启线程
t1.start()
t2.start()
if __name__ == '__main__':
main()
print("程序执行结束了")
-
threading模块提供的方法
- threading。currentThread():返回当前的线程变量
- threading.enumerate():可以列出当前正在运行的所有线程,正在运行的线程:启动后,结束前,不包括启动前和终止后
- threading.activeCount()返回正在运行的线程数量,与len(threading.enumerate)有相同的结果
- 线程.getName():获取线程名称
- 线程.setName():设置线程名称
- 线程.is_alive():判断线程存活状态
def listen(num): for i in range(1,num): print("正在听歌%d"%i) time.sleep(1) def down_load(num): for i in range(1,num): print("正在下载歌曲%d"%i) time.sleep(0.5) def main(): #创建多线程 t1 = threading.Thread(target=down_load,args=(5,)) t2 = threading.Thread(target=listen,args=(5,)) print("t1线程开启之前的状态",t1.is_alive()) #False t1.setName("黄志") print("正在运行的线程数量:",threading.activeCount()) #1 #开启线程 t1.start() print("当前线程:",threading.currentThread()) #MainThread print(threading.enumerate()) #列表中2个线程 print("正在运行的线程数量:", threading.activeCount()) #2 t2.start() print("当前线程:", threading.currentThread()) #MainThread print(threading.enumerate()) #列表中3个线程 print("正在运行的线程数量:", threading.activeCount()) #3 if __name__ == '__main__': main() print("程序执行结束了") print("正在运行的线程数量:", threading.activeCount()) #3
使用继承方式开启线程
定义一个类继承threading.Thread
-
复写父类的run方法
import threading import time class MyThread(threading.Thread): def run(self): for i in range(3): time.sleep(1) msg = f"I'm {self.name} @{i}" print(msg) def test1(): for i in range(5): #创建线程 t = MyThread() #开启线程 t.start() if __name__ == '__main__': test1()
线程之间共享全局变量
def work1():
g_num.append(44)
print(f"--in work1,g_num is {g_num}") #[11,22,33,44]
def work2():
print(f"--in work2,g_num is {g_num}") #[11,22,33,44]
t1 = threading.Thread(target=work1)
t2 = threading.Thread(target=work2)
g_num = [11,22,33]
t1.start()
time.sleep(1)
t2.start()
- 总结:在一个进程内的所有线程共享全局变量,很方便在多个线程共享数据
- 缺点:多线程对全局变量变量随意修改可能会造成数据混乱(线程非安全)
多线程开发可能会遇到的问题
import time
g_num = 0
def work1(num):
global g_num
for i in range(num):
g_num+=1
print(f"--in work1,g_num is {g_num}")
def work2(num):
global g_num
for i in range(num):
g_num+=1
print(f"--in work2,g_num is {g_num}")
t1 = threading.Thread(target=work1,args=(100,))
t2 = threading.Thread(target=work2,args=(100,))
t1.start()
t2.start()
#t1,t2两个线程都要对全局变量g_num进行加1运算,t1,t2各自对g_num加100次,那么最终结果应该是200
#但是由于是多线程同时操作,可能会出现以下问题
#在g_num=0,t1先取得g_num=0,此时系统将t1调为“sleeping”状态,把t2转换成“running”状态,t2也获取g_num=0,
#然后t2对获取到的值进行加1操作并赋值给g_num,使得g_num=1
#然后系统将将t2调为“sleeping”状态,把t1转换成“running”状态,线程t1把获取到的g_num=0加1后赋值给g_nun,使得g_num=1
#这样导致虽然t1和t2都对g_num加1,但结果是1
同步和异步
- 同步:同步就是协同步调,按预定的先后次序进行运行,同是“协同”的意思
- 异步:一个异步调用过程发出后,调用者在没得到结果之前可以进行后续操作。同步和异步是对应的,两个线程要么同步,要么异步
互斥锁
当多个线程几乎同时修改一个共享数据的时候,需要进行同步控制,线程同步能够保证多个 线程安全的访问竞争资源(全局内容),最简单的同步机制就是使用互斥锁
互斥锁是资源的一个引用状态:锁定/非锁定
-
使用过程:某个线程要更改 共享数据时,先将其锁定,此时资源的状态为锁定状态,其他线程就不能更改,直到该线程将 资源状态改为非锁定状态,也就是释放资源,其他的线程才能再次锁定资源
import time g_num = 0 def work1(num): global g_num for i in range(num): mutex.acquire() #锁定 g_num+=1 mutex.release() #释放 def work2(num): global g_num for i in range(num): mutex.acquire() # 锁定 g_num += 1 mutex.release() # 释放 mutex = threading.Lock() t1 = threading.Thread(target=work1,args=(1000000,)) t2 = threading.Thread(target=work2,args=(1000000,)) t1.start() t2.start() while len(threading.enumerate())!=1: time.sleep(1) print(f"--in work2,g_num is {g_num}")
-
锁的好处:
- 确保了某段关键代码只能由一个线程从头到尾完整地执行
-
锁的坏处:
- 阻止了多线程并发执行,包含锁的某段代码实际上只能以单线程模式执行,效率就大大地 下降了
- 由于可以存在多个锁,不同的线程持有不同的锁,并试图获取对方持有的锁时,可能会造成死锁
死锁
-
在多个线程共享资源的时候,如果两个线程同时占有一部分资源,并且同时等待对方的资源,就会造成死锁现象,尽管死锁很少发生,但是一旦发生就会造成应用程序的停止相应
import time def test1(): #lock1上锁 lock1.acquire() print("--test1开始执行--") time.sleep(1) lock2.acquire() print("--test1执行结束--") lock2.release() #lock2解锁 lock1.release()# lock1解锁 def test2(): #lock2上锁 lock2.acquire() print("--test2开始执行--") # lock1上锁 time.sleep(1) lock1.acquire() print("--test2执行结束--") lock1.release() lock2.release() lock1 = threading.Lock() lock2 = threading.Lock() if __name__ == '__main__': t1 = threading.Thread(target=test1) t2 = threading.Thread(target=test2) t1.start() t2.start()
生产者和消费者
-
队列:Python 的 Queue 模块中提供了同步的、线程安全的队列类,包括 FIFO(先入先出)队列 Queue, LIFO(后入先出)队列 LifoQueue,和优先级队列 PriorityQueue。这些队列都实现了锁原语 (可以理解为原子操作,即要么不做,要么就做完),能够在多线程中直接使用。可以使用 队列来实现线程间的同步
from queue import Queue q = Queue(maxsize=3) q.put("黄志") print(q.empty()) #False q.put("古印") q.put("王涛") print(q.full()) # q.put("杨俊") print(q.get()) print(q.get()) print(q.get()) print(q.get()) # print(q.get(timeout=3)) # print(q.get_nowait()) print(q.qsize())
-
生产者和消费者
- 生产者:生成数据的线程
- 消费者:处理数据的线程
- 目的:平衡生产者和消费者的工作能力来提高程序的整理处理数据的速度
import time import queue def producer(name):#生产者 count = 1 while True: print("%s生产了包子%d"%(name,count)) q.put(count) count+=1 time.sleep(1) print("包子总数:",q.qsize()) def costom(name):#消费者 while True: print("%s吃了包子%s"%(name,q.get())) time.sleep(0.5) if __name__ == '__main__': q = queue.Queue(maxsize=5) t1 = threading.Thread(target=producer,args=("黄志",)) t2 = threading.Thread(target=costom,args=("吃货古",)) t3 = threading.Thread(target=costom, args=("吃货刘",)) t1.start() t2.start() t3.start()
-
为什么使用生产者消费者模式
-
在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当
中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理
完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必
须等待生产者。为了解决这个问题于是引入了生产者和消费者模式
-
-
什么是生产者消费者模式
- 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼 此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费
者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队 列就相当于一个缓冲区,平衡了生产者和消费者的处理能力
- 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼 此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费