守护线程
守护进程(守护线程)会等待主进程(主线程)运行完毕后被销毁
运行完毕并非终止运行:
1.对主进程来说:运行完毕指的是主进程代码运行完毕
2.对主线程来说:运行完毕指的是主线程所在的进程内所有非守护线程统统运行完毕,主线程才算运行完毕
详细解释:
1.主进程在其代码结束后就已经算运行完毕了(守护进程在此时就被回收),然后主进程会一直等非守护的子进程都运行完毕后回收子进程的资源(否则会产生僵 尸进程),才会结束。
2.主线程在其他非守护线程运行完毕后才算运行完毕(守护线程在此时就被回收)。因为主线程的结束意味着进程的结束,进程整体的资源都将被回收,而进程 必须保证非守护线程都运行完毕后才能结束。
from threading import Thread import time def sayhi(name): time.sleep(2) print('%s say hello' %name) if __name__ == '__main__': t=Thread(target=sayhi,args=('egon',)) # t.setDaemon(True) #必须在t.start()之前设置 t.daemon=True t.start() print('主线程') print(t.is_alive()) ''' 打印结果 主线程 True ''' # 思考下述代码的执行结果有可能是哪些情况?为什么? from threading import Thread import time def foo(): print(123) time.sleep(1) print("end123") def bar(): print(456) time.sleep(3) print("end456") if __name__ == '__main__': t1=Thread(target=foo) t2=Thread(target=bar) t1.daemon=True t1.start() t2.start() print("main-------") #必须等到非守护线程执行完毕,才结束 ''' 打印结果: 123 456 main------- end123 end456 '''
互斥锁
在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势。
GIL并不是Python的特性,Python完全可以不依赖于GIL。像其中的JPython就没有GIL。
互斥锁:
原理:将并行变成串行
精髓:局部串行,只针对共享数据修改 保护不同的数据就应该用不用的锁
''' 互斥锁可以保证程序的安全性提高,但是效率下降 ''' from threading import Thread,Lock import time n = 100 def task(): global n mutex.acquire() # 99个线程都停在这等待抢锁 temp = n time.sleep(0.1) # 没加锁之前 睡了0.1秒,100个线程都停到这了 n=temp-1 mutex.release() if __name__ == '__main__': mutex=Lock() t_l=[] for i in range(100): t = Thread(target=task) t_l.append(t) t.start() for t in t_l: t.join() print('主线程',n) ''' 打印结果: 主线程 0 '''
GIL(global interpreter lock)
GIL本身就是互斥锁
在一个python的进程内,不仅有test.py的主线程或者由该主线程开启的其他线程,还有解释器开启的垃圾回收等解释器级别的线程,总之,所有线程都运行在这一 个进程内,毫无疑问
1.所有数据都是共享的,这其中,代码作为一种数据也是被所有线程共享的(test.py的所有代码以及Cpython解释器的所有代码)
例如:test.py定义一个函数work(代码内容如下图),在进程内所有线程都能访问到work的代码,于是我们可以开启三个线程然后target都指向该代码,能访问 到意味着就是可以执行。
2.所有线程的任务,都需要将任务的代码当做参数传给解释器的代码去执行,即所有的线程要想运行自己的任务,首先需要解决的是能够访问到解释器的代码。
执行流程:
target=work
多个线程先访问到解释器的代码,即拿到执行权限,然后将target的代码交给解释器的代码去执行
解释器的代码是所有线程共享的,所以垃圾回收线程也可能访问到解释器的代码而去执行,这就导致了一个问题:对于同一个数据100,可能线程1执行x=100的同 时,而垃圾回收执行的是回收100的操作,解决这种问题没有什么高明的方法,就是加锁处理,如下图的GIL,保证python解释器同一时间只能执行一个任务的代码
GIL与Lock
GIL: 是解释器级别的锁,用来保护解释器相关的数据,例如:回收垃圾
Lock: 是应用程序的锁,用来保护应用程序相关的数据
分析 :
1、100个线程去抢GIL锁,即抢执行权限
2、肯定有一个线程先抢到GIL(暂且称为线程1),然后开始执行,一旦执行就会拿到lock.acquire()
3、极有可能线程1还未运行完毕,就有另外一个线程2抢到GIL,然后开始运行,但线程2发现互斥锁lock还未被线程1释放,于是阻塞,被迫交出执行权限,即释放GIL
4、直到线程1重新抢到GIL,开始从上次暂停的位置继续执行,直到正常释放互斥锁lock,然后其他的线程再重复2 3 4的过程