38 线程
回顾:
生产者消费者模型
主要为了解决强耦耦合的问题
队列
from multiprocessing import Queue
先进先出
队列本身是安全的
from multiprocessing import JoinableQueue
task_done() 每消费一个数据,就返回一个标识
join() 接收task_done返回的标识,以便可以知道队列中的数据什么时候被消费完了
管道(了解)
本身是不安全的
from multiprocessing import Pipe con1,con2 = Pipe()
con1可以收发数据,con2也可以收发数据(全双工)
队列 = 管道 + 锁
多进程之间共享内存数据:
from multiprocessing import Manager,Value
进程池
from multiprocessing import Pool p.map(func,iterable) p.apply(func,args=()) 同步的做任务 池中的进程是普通进程,不会跟随着主进程的结束而结束。 p.apply_async(func,args=(),callback=None) 异步,记得加上close和join close: 不再接受新的任务,准备关闭 join: 等待进程池中所有进程执行完毕任务。
池中的进程是守护进程,主进程的代码执行完毕,守护进程就结束了
在进程池中的回调函数是主进程调用的,和子进程无关。
1 学习线程
线程被称作轻量级的进程。 GIL:全局解释锁(只有Cpython解释器才有)
对于线程来说,因为有了GIL,所以没有真正的并行
计算机的执行单位以线程为单位。计算机的最小可执行是线程。
进程是资源分配的基本单位。线程是可执行的基本单位,是可被调度的基本单位。
线程不可以自己独立拥有资源。线程的执行,必须依赖于所属进程中的资源。
进程中必须至少应该有一个线程。
线程又分为用户级线程和内核级线程(了解)
用户级线程:对于程序员来说的,这样的线程完全被程序员控制执行,调度
内核级线程:对于计算机内核来说的,这样的线程完全被内核控制调度。
进程由 代码段 数据段 PCB组成(process control block)
线程由 代码段 数据段 TCB组成(thread control block)
线程和进程的比较
thread - 线程
import thread 操作线程的模块
import threading 用这个去操作线程
(1) cpu切换进程要比cpu切换线程 慢很多
在python中,如果IO操作过多的话,使用多线程最好了
from multiprocessing import Process from threading import Thread import time def func(): pass if __name__ == '__main__': start = time.time() for i in range(100): p = Process(target=func) p.start() print('开100个进程的时间:', time.time() - start) start = time.time() for i in range(100): p = Thread(target=func) p.start() print('开100个线程的时间:', time.time() - start) ============ 开100个进程的时间: 0.2928299903869629 开100个线程的时间: 3.7468421459198
(2) 在同一个进程内,所有线程共享这个进程的pid,也就是说所有线程共享所属进程的所有资源和内存地址
from multiprocessing import Process from threading import Thread import time,os def func(name): print('我是一个%s,我的pid是%s'%(name,os.getpid())) if __name__ == '__main__': print('我是main,我的pid是%s'%(os.getpid())) for i in range(10): p = Process(target=func,args=('进程',)) p.start() for i in range(10): p = Thread(target=func,args=('线程',)) p.start()
主进程和子进程的pid 是不同的
主线程和子线程的pid 是相同的
(3) 在同一个进程内,所有线程共享该进程中的全局变量
from multiprocessing import Process from threading import Thread,Lock import time,os def func(): global num tmp = num time.sleep(0.00001) num = tmp - 1 if __name__ == '__main__': num = 100 t_l = [] for i in range(100): t = Thread(target=func) t.start() t_l.append(t) # time.sleep(1) [t.join() for t in t_l] print(num)
(4) 因为有GIL锁的存在,在Cpython中,没有真正的线程并行。但是有真正的多进程并行
当你的任务是计算密集的情况下,使用多进程好
总结:在CPython中,IO密集用多线程,计算密集用多进程
(5)关于守护线程和守护进程的事情(注意:代码执行结束并不代表程序结束)
守护进程:要么自己正常结束,要么根据父进程的代码执行结束而结束
守护线程:要么自己正常结束,要么根据父线程的执行结束而结束
2线程的使用方法
import threading from threading import Thread import time def func(): print('这是一个子线程') time.sleep(2) if __name__ == '__main__': t = Thread(target=func,args=()) t.start()
import threading from threading import Thread import time class MyThread(Thread): def __init__(self): super(MyThread, self).__init__() def run(self): print('我是一个子线程') t = MyThread() t.start() # 运行类中的run()方法 ,也开启了线程
from multiprocessing import Process from threading import Thread,Lock import time,os l = Lock()# 一把钥匙配一把锁 l.acquire() print('abc') l.acquire()# 程序会阻塞住 陷入死锁了 print(123)
from multiprocessing import Process from threading import Thread,Lock import time,os def man(l_tot,l_pap): l_tot.acquire()# 是男的获得厕所资源,把厕所锁上了 print('alex在厕所上厕所') time.sleep(1) l_pap.acquire()# 男的拿纸资源 print('alex拿到卫生纸了!') time.sleep(0.5) print('alex完事了!') l_pap.release()# 男的先还纸 l_tot.release()# 男的还厕所 def woman(l_tot,l_pap): l_pap.acquire() # 女的拿纸资源 print('小雪拿到卫生纸了!') time.sleep(1) l_tot.acquire() # 是女的获得厕所资源,把厕所锁上了 print('小雪在厕所上厕所') time.sleep(0.5) print('小雪完事了!') l_tot.release() # 女的还厕所 l_pap.release() # 女的先还纸 if __name__ == '__main__': l_tot = Lock() l_pap = Lock() t_man = Thread(target=man,args=(l_tot,l_pap)) t_woman = Thread(target=woman,args=(l_tot,l_pap)) t_man.start() t_woman.start()
from multiprocessing import Process from threading import Thread,RLock import time,os # RLock是递归锁 --- 是无止尽的锁,但是所有锁都有一个共同的钥匙 # 想解决死锁,配一把公共的钥匙就可以了。 def man(l_tot,l_pap): l_tot.acquire()# 是男的获得厕所资源,把厕所锁上了 print('alex在厕所上厕所') time.sleep(1) l_pap.acquire()# 男的拿纸资源 print('alex拿到卫生纸了!') time.sleep(0.5) print('alex完事了!') l_pap.release()# 男的先还纸 l_tot.release()# 男的还厕所 def woman(l_tot,l_pap): l_pap.acquire() # 女的拿纸资源 print('小雪拿到卫生纸了!') time.sleep(1) l_tot.acquire() # 是女的获得厕所资源,把厕所锁上了 print('小雪在厕所上厕所') time.sleep(0.5) print('小雪完事了!') l_tot.release() # 女的还厕所 l_pap.release() # 女的先还纸 if __name__ == '__main__': l_tot = l_pap = RLock() t_man = Thread(target=man,args=(l_tot,l_pap)) t_woman = Thread(target=woman,args=(l_tot,l_pap)) t_man.start() t_woman.start()
# 第一种情况 在同一个线程内,递归锁可以无止尽的acquire,但是互斥锁不行
# 第二种情况,在不同的线程内,递归锁是保证只能被一个线程拿到钥匙,然后无止尽的acquire,其他线程等待
(1)锁机制
递归锁
RLock() 可以有无止尽的锁,但是会有一把万能钥匙(获得的不是同一个锁,而是锁中锁,不停迭代)
互斥锁:
Lock() 一把钥匙配一把锁
GIL:全局解释器锁
锁的是线程,是CPython解释器上的一个锁,锁的是线程,意思是在同一时间只允许一个线程访问cpu
(2) 信号量:
from threading import Semaphore
去看多进程的信号量
from threading import Semaphore,Thread import time def func(sem,i): sem.acquire() print('第%s个人进入屋子'%i) time.sleep(2) print('第%s个人离开屋子'%i) sem.release() sem = Semaphore(20) for i in range(20): t = Thread(target=func,args=(sem,i)) t.start()
(3) 事件
from threading import Event
去看多进程的事件机制
(4) 条件
from threading import Condition
条件是让程序员自行去调度线程的一个机制
# Condition涉及4个方法
# acquire()
# release()
# wait() 是指让线程阻塞住
# notify(int) 是指给wait发一个信号,让wait变成不阻塞
# int是指,你要给多少给wait发信号
from threading import Thread,Condition import time # Condition涉及4个方法 # acquire() # release() # wait() 是指让线程阻塞住 # notify(int) 是指给wait发一个信号,让wait变成不阻塞 # int是指,你要给多少给wait发信号 def func(con,i): con.acquire() con.wait()# 线程执行到这里,会阻塞住,等待notify发送信号,来唤醒此线程 con.release() print('第%s个线程开始执行了!'%i) if __name__ == '__main__': con = Condition() for i in range(10): t = Thread(target=func,args=(con,i)) t.start() while 1: num = int(input(">>>")) con.acquire() con.notify(num)# 发送一个信号给num个正在阻塞在wait的线程,让这些线程正常执行 con.release()
from threading import Condition,Thread import time def func(con,i): con.acquire()# 主线程和10个子线程都在抢夺递归锁的一把钥匙。 # 如果主线程抢到钥匙,主线程执行while 1,input,然后notify发信号,还钥匙。但是,此时如果主线程执行特别快 # 极有可能接下来主线程又会拿到钥匙,那么此时哪怕其他10个子线程的wait接收到信号,但是因为没有拿到钥匙,所以其他子线程还是不会执行 con.wait() print('第%s个线程执行了'%i) con.release() con = Condition() for i in range(10): t = Thread(target=func,args = (con,i)) t.start() while 1: # print(123) con.acquire() num = input('>>>') con.notify(int(num)) con.release() time.sleep(0.5) # 主线程执行太快了 ,子线程还没来得及摸到到钥匙(还没到门口),咱们就在这搞循环,子线程根本穿不进去 # 条件 涉及 4个方法: # con.acquire() # con.release() # con.wait() # 假设有一个初始状态为False,阻塞。一旦接受到notify的信号后,变为True,不再阻塞 # con.notify(int) 给wait发信号,发int个信号,会传递给int个wait,让int个线程正常执行
(5) 定时器
from threading import Timer
# Timer(time,func)
# time:睡眠的时间,以秒为单位
# func:睡眠时间之后,需要执行的任务
from threading import Timer# 定时器 def func(): print('就是这么nb!') Timer(2.5,func).start() # Timer(time,func) # time:睡眠的时间,以秒为单位 # func:睡眠时间之后,需要执行的任务
今天的面试题:
进程和线程的区别?
你认为什么时候用多线程好?什么时候用多进程好?
给你一个任务场景,让你去分析,如果让你去研发,你是选择用多线程还是多进程?
解释以下GIL锁?