一、线程的定义
了解线程之前,我们先了解下进程,进程实际上不是一个执行的单位,它是一个资源单位,每个进程中都会自带一个线程,所以说线程才是cpu上的执行单位。
形象的理解:
若将操作系统比喻成一工厂
工厂内每建成一个车间,我们可以将之比喻为启动一个进程,在每个车间内,都至少有一条流水线,这个可比喻为每个进程内至少有一个线程,所以说,每个进程之中都至少有一个线程。
二、线程 VS 进程
1.过程的区别
线程是单指代码的执行过程
进程是负责资源空间的申请与销毁的过程
2.内存数据的共享性
多个进程的内存之间的空间是彼此隔离的,所以进程间是没有数据共享的。
同一个进程下的多个线程可以共享该进程的数据。
注意:进程与进程之间的线程数据是不共享的。
1 from threading import Thread 2 3 x=100 4 def task(): 5 global x 6 x=0 7 8 if __name__ == '__main__': 9 t=Thread(target=task,) 10 t.start() 11 t.join() 12 print('主',x) 13 14 # 进程的数据共享性验证方法是一样的
3.创建速度的比较
线程的创建速度要远远大于创建进程的速度。线程因为是不需要重新申请空间的,几乎是在指令刚发出去时几乎同步造好,而进程还需得向操作系统申请一个空间,所以速度会远远慢于造线程的速度。
1 from threading import Thread 2 from multiprocessing import Process 3 import time 4 5 def task(name): 6 print('%s is running' %name) 7 time.sleep(3) 8 print('%s is done' %name) 9 10 if __name__ == '__main__': 11 t=Thread(target=task,args=('子线程',)) 12 # t=Process(target=task,args=('子进程',)) 13 t.start() 14 print('main') 15 16 # 如需验证,子线程与子进程同一时间只能存在一个
4.查看pid
1 from threading import Thread 2 import time,os 3 4 def task(): 5 print(os.getpid()) 6 7 if __name__ == '__main__': 8 t=Thread(target=task,) 9 t.start() 10 print('main',os.getpid()) 11 12 # 结果证明'主线程'与'子线程'的id号是一样的
三、开启线程的两种方式
3.1 方式一
from threading import Thread,current_thread import time def task(): print('%s is going' %current_thread().name) time.sleep(3) print('%s is done' %current_thread().name) if __name__ == '__main__': t=Thread(target=task,args=()) t.start() print('---main---')
3.2 方式二
from threading import Thread import time class Mythread(Thread): def run(self): print('%s is running' %self.name) time.sleep(3) print('%s is done' %self.name) if __name__ == '__main__': t=Mythread() t.start() print('---main---')
四、线程对象的其他方法和属性
主进程等子进程运行结束的目的:给子进程 "收尸"。
而进程必须等待其内部的所有线程运行完毕才能结束。
from threading import Thread,current_thread import time def task(): print('%s is running' %current_thread().name) time.sleep(3) print('%s is done' %current_thread().name) if __name__ == '__main__': t=Thread(target=task,) t.start() print('---main---')
1 from threading import Thread,current_thead,active_count,enumerate 2 3 # Thread 造线程 4 # current_thread 当前线程的默认名称 5 # active_count 活跃的线程数(包括自带的线程) 6 # enumerate 将活跃的线程收集起来,放至一个列表当中
五、守护线程
守护线程会在该进程内的所有非守护线程都运行结束之后才跟着结束。即守护线程其实守护的是整个进程的运行周期。
1 from threading import Thread 2 import time 3 def foo(): 4 print(123) 5 time.sleep(3) 6 print("end123") 7 8 def bar(): 9 print(456) 10 time.sleep(1) 11 print("end456") 12 13 14 t1=Thread(target=foo) 15 t2=Thread(target=bar) 16 17 t1.daemon=True # 将t1设成守护线程 18 t1.start() 19 t2.start() 20 print("---main---") 21 22 # result(之所以没有运行 end123,是因为t1是守护线程,所有的非守护线程运行完毕之 23 #后,t1也跟着结束掉) 24 25 123 26 456 27 ---main--- 28 end456
六、互斥锁
1.互斥锁
1 from threading import Thread,Lock 2 import time 3 4 mutex=Lock() 5 6 x=100 7 def task(): 8 global x 9 # mutex.acquire() 10 temp=x 11 time.sleep(0.1) 12 x=temp-1 13 # mutex.release() 14 15 if __name__ == '__main__': 16 t_l=[] 17 start=time.time() 18 for i in range(100): 19 t=Thread(target=task) 20 t_l.append(t) 21 t.start() 22 23 for t in t_l: 24 t.join() 25 26 stop=time.time() 27 print(x,stop-start)
小例子的解释:
上述的例子中,我们并没有上锁,结果是99,为啥不是0的原因:首先,造出100个 "子线程"几乎是一瞬间的事情,因为同一个进程下的所有线程之间数据是共享的,所以它们都取到了 x=100,然后又将 x 赋值给另一个变量 temp ,在睡了0.1s之后,所有"子线程"中的 temp 都减去了1,所以我们取到的结果就是99而不是0。(我的简单理解就是各个 "子进程" 之间各自为战)
1 from threading import Thread,Lock 2 import time 3 4 mutex=Lock() 5 6 x=100 7 def task(): 8 global x 9 mutex.acquire() 10 x=x-1 11 time.sleep(0.1) 12 mutex.release() 13 14 if __name__ == '__main__': 15 t_l=[] 16 start=time.time() 17 for i in range(100): 18 t=Thread(target=task) 19 t_l.append(t) 20 t.start() 21 22 for t in t_l: 23 t.join() 24 25 stop=time.time() 26 print(x,stop-start)
若我们将其上锁,其结果就变成了0,其原因是:在多个线程被造出来之后,几乎可以认为是第一个被造出来的线程最先抢到了锁,其他线程只能在外面等了,只有在抢到锁的线程执行完锁内的代码并"放开锁"之后,其它的线程才有机会去继续争抢锁,但是此时 x 的值已经被最先抢到锁的线程更改成了99,所以第二个抢到锁的线程取到的值就是99了,第二个抢到锁的线程再次对 x 进行更爱,以此类推,最后得到 x 的值即为0。
综上所述,互斥锁的概念就是将无序化变得有序。
2.死锁现象与递归锁
1 from threading import Thread,Lock,active_count,RLock 2 import time 3 4 mutexA=Lock() 5 mutexB=Lock() 6 7 class Mythread(Thread): 8 def run(self): 9 self.f1() 10 self.f2() 11 12 def f1(self): 13 mutexA.acquire() 14 print('%s got A' %self.name) 15 16 mutexB.acquire() 17 print('%s got B' %self.name) 18 mutexB.release() 19 20 mutexA.release() 21 22 def f2(self): 23 mutexB.acquire() 24 print('%s got B' %self.name) 25 time.sleep(1) 26 27 mutexA.acquire() 28 print('%s got A' %self.name) 29 mutexA.release() 30 31 mutexB.release() 32 33 if __name__ == '__main__': 34 for i in range(10): 35 t=Mythread() 36 t.start()
死锁现象的解释:线程造出的速度very very fast,但同时又必须是有速度之分的,所以我们几乎可以认为是第一个线程(在这里我们称为first)最先抢到了 A 和 B (因为此时是没有竞争者出现的),当 first 将 A 和 B 从手中扔掉之后,有立马捡起了 B ,然后睡了1s,此时,其他的线程肯定是有一个线程(假装是线程二把---second)捡到了 A ,剩下的线程也只能是干瞪眼了,在 first 睡醒之后。此时问题就出现了,first 需要 second 手中的 A ,而 second 又需要 first 手中的 B,谁也不肯谦让,这时就造成了死锁现象。
1 from threading import Thread,RLock 2 import time 3 4 # mutexA=Lock() 5 # mutexB=Lock() 6 obj=RLock() #递归锁的特点:可以连续的acquire 7 mutexA=obj 8 mutexB=obj 9 10 class Mythread(Thread): 11 def run(self): 12 self.f1() 13 self.f2() 14 15 def f1(self): 16 mutexA.acquire() 17 print('%s got A' %self.name) 18 19 mutexB.acquire() 20 print('%s got B' %self.name) 21 mutexB.release() 22 23 mutexA.release() 24 25 def f2(self): 26 mutexB.acquire() 27 print('%s got B' %self.name) 28 time.sleep(1) 29 30 mutexA.acquire() 31 print('%s got A' %self.name) 32 mutexA.release() 33 34 mutexB.release() 35 36 if __name__ == '__main__': 37 for i in range(10): 38 t=Mythread() 39 t.start()
递归锁解决死锁的原因:递归锁是一把可以连续被同一个线程 acquire 的锁。当一个线程1捡到锁之后,会在锁上作一个标记,此时锁的引用计数为1,当线程1继续 acquire 这把锁的时候,引用计数将变成2,依次递增,此时,其余的线程均不能抢到该锁,只能在外面等待了。若线程1将锁的引用计数变成0之后,所有的线程就又可开始重新争抢这把锁了(包括线程1),但此时就不一定是线程1抢到该锁了,而是随机的,所以,由此就可解决死锁现象。
七、信号量
1.定义
信号量指的就是控制同一时刻并发执行的任务数。
from threading import Thread,Semaphore,current_thread import time,random sm=Semaphore(5) # 参数可以设置,此处表示最多允许5个 # 只有坐在座位上的人离开之后,后面的人才有机会 def task(): with sm: print('%s 正在座位上' %current_thread().name) time.sleep(random.randint(1,4)) if __name__ == '__main__': for i in range(20): t=Thread(target=task) t.start()