线程理论
什么是线程:指令的执行过程。
在进程中存在线程,进程如果没有线程无法运行,进程只是负责提供内存资源,线程真正进入CPU,是CPU最小的运行单位。多线程就是为了一个进程执行多个任务。
线程的创建销毁被进程快,且不像进程那样内存隔离,可以不需要额外操作就可以实现数据共享。
并发的时候,多进程加多线程;多核中也可以应用多线程。
线程的两种创建方式(重点)main可写可不写
1 from threading import Thread 2 第一种 3 def f1(n): 4 print(1+n) 5 6 def f2(n): 7 print(2) 8 9 if __name__ == '__main__': 10 t1 = Thread(target=f1,args=(0,)) #与进程创建相同 11 t2 = Thread(target=f2,args=(0,)) #与进程创建相同 12 t1.start() 13 t2.start() 14 print('主线程') 15 第二种 16 class MyThread(Thread): 17 def __init__(self,name): 18 super().__init__() 19 self.name = name 20 21 def run(self): 22 print(self.name) 23 24 if __name__ == '__main__': 25 t= MyThread('cc') 26 t.start() 27 print('主线程')
查看线程pid线程(了解)
os.getpid(),显示都是同一个进程号
线程可以直接数据共享 等待线程执行完毕的方法为join(),用法同进程
与进程效率对比 创建时间差了将近百倍
线程空间不是隔离的
Io密集型切换提高代码效率;计算密集型切换降低代码效率。
计算密集型,运行时间加长,此时多线程无法应用多核技术,所有多线程此时反而比多进程慢
守护线程
守护线程等待所有非守护线程结束才结束,一旦别人都结束,而守护进程没结束的话也自杀
进程只守护主进程
线程锁/互锁(重点)
1 from threading import Lock,Thread 2 import time 3 4 num = 100 5 def f1(): 6 global num 7 temp = num 8 temp -= 1 9 time.sleep(1) 10 num = temp 11 12 if __name__ == '__main__': 13 t_list = [] 14 15 for i in range(10): 16 t = Thread(target=f1,) 17 t.start() 18 t_list.append(t) 19 20 [t.join() for tt in t_list] 21 22 print('主线程的num:',num) 23 此时无法得到90,十个线程的操作在相同,瞬间开启, 24 每个人线程拿到的temp可能都是100,最后赋值回去为99,可能是拿到99,变成了99 25 26 num = 100 27 28 29 def f1(loc): 30 global num #只是声明一个变量,不会拿到数据,不存在赋值 31 loc.acquire() 32 temp = num 33 temp -= 1 34 time.sleep(0.1) 35 num = temp 36 loc.release() 37 38 39 if __name__ == '__main__': 40 t_list = [] 41 t_loc = Lock() 42 for i in range(10): 43 t = Thread(target=f1,args=(t_loc,) ) 44 t.start() 45 t_list.append(t) 46 47 [t.join() for tt in t_list] 48 49 print('主线程的num:', num) 50 51 #加锁,用法同进程锁
死锁(重点)
在锁的代码中再完成地嵌套一个锁时发生,
A中嵌套一个完整的B,B中嵌套一个完整的A,两个在想抢对方已有的锁的时候都会卡死,相互锁死
递归锁(重点)
破除死锁,同一把锁多个形式,RLock()
内含计数器,创建一次就加一,释放一个就减一,为零才可以抢
GIL锁:
在全局的cpython解释器上加的一把锁
解释器用于解释自己的代码为二进制
同一时间,只能有一个线程可以进入cpython解释器,导致py不能使用多核技术。想多线程并发依旧是可以搞的,但是就需要开多进程来实现了,因为就是又多开了一个解释器。
线程可以节约io密集型程序的时间,计算密集型则不行。一般进程按照cpu的个数开。
子进程不能input,因为输入台只能由主进程使用,输出内容各个进程都可以,但是并发输出可能会打印混乱。 子线程可以输入,因为同属一个进程。