04:全局解释器锁(GIL)
0 pypy(没有全局解释器锁) cpython(99.999999%)
-pypy python好多模块用不了,
1 全局解释器锁,GIL锁(cpython解释器的问题)
-当年python设计的时候,还是单核,没有多核的概念
-python需要做垃圾回收(gc)
-垃圾回收线程,进行垃圾回收
-设计了一个大锁(GIL锁),只有拿到这把锁的线程,才能执行
-同一时刻,在一个进程中,可以开多个线程,但是只能有一条线程在执行
-不能利用多核优势
只针对与cpython解释器(其他解释器,包括其他语言不这样)
2 如果是计算密集型:要开进程
3 如果是io密集型:要开线程
2 开启线程的两种方式
from threading import Thread import time # # def task(): # time.sleep(1) # print('我是子线程') # # # if __name__ == '__main__': # t=Thread(target=task) # t.start() # print('我是主线程') # # ###第二种方式 class MyThread(Thread): def __init__(self,a): self.a=a super().__init__() def run(self): time.sleep(1) print('我是子线程',self.a) if __name__ == '__main__': t=MyThread('aaaaa') t.start() print('我是主线程')
3 多线程与多进程比较
3.1 pid比较
3.2 开启速度比较
#开线程消耗的资源,耗费的时间远远小于开进程 from threading import Thread import time import os from multiprocessing import Process def task(): time.sleep(0.1) print('我是子线程') if __name__ == '__main__': ####线程 # ctime=time.time() # t=Thread(target=task) # t.start() # t.join() # 等待子线程执行完成主线程再执行 # print('我是主线程') # print(time.time()-ctime) ##进程 ctime=time.time() t=Process(target=task) t.start() t.join() # 等待子线程执行完成主线程再执行 print('我是主线程') print(time.time()-ctime)
3.3 内存数据的共享问题
##线程间数据共享 from threading import Thread import time import os from multiprocessing import Process def task(): global n n=10 print(n) if __name__ == '__main__': ####线程 n=100 t=Thread(target=task) t.start() t.join() # 等待子线程执行完成主线程再执行 print('我是主线程') print(n)
4 Thread类的其他方法
from threading import Thread import threading import time def task(): # time.sleep(0.01) #在子线程中执行 # res = threading.currentThread() # print(res) res=threading.get_ident() print('子线程:',res) print('我是子线程') if __name__ == '__main__': t=Thread(target=task) t1=Thread(target=task) t.start() t1.start() # print(t.is_alive()) #看线程是否存活 # # print(t.getName() ) # 获取线程的名字 # t.setName('lqz') # 设置线程民资 # print(t.getName() ) # # # print('主线程') # time.sleep(0.02) # print(t.is_alive()) # 主线程中执行,返回当前线程对象 # res=threading.currentThread() # print(res) # 返回当前进程中正在运行的子线程对象列表 # res=threading.enumerate() # print(res) # 返回当前正在运行的线程个数 # res=threading.activeCount() # print(res) # 线程id号 res=threading.get_ident() print('主线程:',res) ''' t.is_alive() t.getName() t.setName('lqz') threading:模块下的一些方法 res=threading.currentThread() res=threading.enumerate() res=threading.activeCount() res=threading.get_ident() '''
5 join方法
等待子线程执行结束
from threading import Thread import time def task(): time.sleep(2) print('我是子线程') if __name__ == '__main__': ll=[] for i in range(1000): t=Thread(target=task) t.start() ll.append(t) for i in ll: i.join() # 主线程等待子线程执行完再执行 print('我是主线程,子线程全都执行完了')
6 守护线程
from threading import Thread import time def task(): time.sleep(2) print('我是子线程') if __name__ == '__main__': t=Thread(target=task) t.setDaemon(True) # 如果主线程执行结束,子线程也结束(不执行了) t.start() #只要主线程执行结束,子线程也结束 print('主线程执行结束')
7 同步锁(互斥锁)
## 多个线程操作同一个数据(变量),会出现并发安全的问题 # from threading import Thread,Lock # import time # import random # def task(): # global n # # # ### 临界区(加锁) # time.sleep(random.random()) # temp=n # time.sleep(random.random()) # temp=temp-1 # n=temp # # ##模拟不出来,因为太快了,没有cup的切换(io,时间片到了),模拟io,让cpu切换 # # # n-=1 # # # if __name__ == '__main__': # n=10 # ll=[] # for i in range(10): # t=Thread(target=task) # t.start() # ll.append(t) # # for i in ll: # i.join() # # # print(n) ###出现了并发安全的问题,加锁解决 from threading import Thread,Lock import time import random def task_lock(lock): global n ### 临界区(加锁) with lock: time.sleep(random.random()) temp=n time.sleep(random.random()) temp=temp-1 n=temp ##模拟不出来,因为太快了,没有cup的切换(io,时间片到了),模拟io,让cpu切换 # n-=1 def task_no_lock(): global n time.sleep(random.random()) temp=n time.sleep(random.random()) temp=temp-1 n=temp if __name__ == '__main__': n=10 lock=Lock() ll=[] for i in range(10): # t=Thread(target=task_lock,args=[lock,]) t=Thread(target=task_no_lock,args=[lock,]) t.start() ll.append(t) t.join() # for i in ll: # i.join() print(n) ''' 互斥锁和join的区别 如果使用互斥锁:只锁临界区,只有临界区是串行,其他地方还是并发的 如果使用join,整个过程全变成串行执行 '''
8 信号量
### 信号量可以理解为多把锁,同时允许多个线程来更改数据 from threading import Thread,Semaphore import time import random def task(sm,i): sm.acquire() print('%s:这个人在上厕所'%i) time.sleep(random.random()) print('%s:这个人拉完了'%i) sm.release() sm=Semaphore(5) for i in range(40): t=Thread(target=task,args=[sm,i]) t.start()
9 Event事件
Event事件: 一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号 比如一个线程等待另一个线程执行结束再继续执行 # 一些线程需要等到其他线程执行完成之后才能执行,类似于发射信号 # 比如一个线程等待另一个线程执行结束再继续执行 from threading import Thread, Event import time import random def girl(event): print('赵丽颖现在在结婚状态') time.sleep(1) # 离婚了,发送信号 print('赵丽颖离婚了') event.set() # 发送一个信号 def boy(i, event): print('屌丝%s:在等赵丽颖的离婚信号'%i) event.wait() # 收不到信号之前,一直卡在这 print('屌丝%s号,收到了离婚信号,开始追' % i) event = Event() t = Thread(target=girl, args=[event, ]) t.start() for i in range(10): t1 = Thread(target=boy, args=[i, event]) t1.start() ## 写两条线程,一条线程读一个文件的头2分之一,另一个线程读这个文件的后2分之一,但是必须第一个线程读完,发送信号后,第二个线程才能读
总结
1 GIL锁:全局解释器锁,在解释器之上的一把大锁,线程必须获得这把锁,才能执行,只针对与cpython解释器 2 GIL和线程锁有什么区别?有了GIL锁,为什么还要线程锁? -本身GIL和线程锁,都是线程级别的锁,GIL是内置的,解释器里的 -线程锁:开发者定义的 3 多核cpu: 如果是计算密集型:开进程 io密集型:开线程 4 开启线程的两种方式(对比进程) 5 进程和线程的比较 -进程id比较 -开启效率的比较 -共享变量 6 Thread类的其他方法,threading模块下的其他方法 7 线程join(等待子线程执行完成) 8 守护线程(如果主线程执行完成,子线程也结束) 9 互斥锁,同步锁:为了保证并发情况下数据安全,把对数据的操作过程变成串行,牺牲了效率,保证了数据安全 10 信号量,Event
愿君前程似锦,归来仍是少年