线程-线程的(锁、死锁现象、守护线程)

一、线程

什么是线程:
    一个进程内最少自带一个线程,其实进程根本不能执行,进程不是执行单位,是资源的单位,分配资源的单位,线程才是执行单位;进程是最小的内存分配单位.
什么是多线程:
    多线程(即多个控制线程)的概念是,在一个进程中存在多个控制线程,多个控制线程共享该进程的地址空间,相当于一个车间内有多条流水线,
都共用一个车间的资源 有了进程为啥还要线程:
1.进程只能在一个时间干一件事,如果想同时干两件事或多件事,进程就无能为力了。   2.进程在执行的过程中如果阻塞,例如等待输入,整个进程就会挂起,即使进程中有些工作不依赖于输入的数据,也将无法执行。
关于程序结束的解释:
1.线程全部结束,程序的才真正结束
   2.线程之间资源共享,共享的是同一个进程中的资源,这样有数据安全隐患(解决的办法是加锁,递归锁(RLock)和同步锁(Lock))
   3.线程不需要写 if __name__=="__main__" 方法
  4.线程里面可以写input,但是子进程里面不可以写input.
1 如果这两个缺点理解比较困难的话,举个现实的例子也许你就清楚了:如果把我们上课的过程看成一个进程的话,那么我们要做的是耳朵听老师讲课,手上还要记笔记,脑子还要思考问题,这样才能高效的完成听课的任务。而如果只提供进程这个机制的话,上面这三件事将不能同时执行,同一时间只能做一件事,听的时候就不能记笔记,也不能用脑子思考,这是其一;如果老师在黑板上写演算过程,我们开始记笔记,而老师突然有一步推不下去了,阻塞住了,他在那边思考着,而我们呢,也不能干其他事,即使你想趁此时思考一下刚才没听懂的一个问题都不行,这是其二。
2     现在你应该明白了进程的缺陷了,而解决的办法很简单,我们完全可以让听、写、思三个独立的过程,并行起来,这样很明显可以提高听课的效率。而实际的操作系统中,也同样引入了这种类似的机制——线程。
举例来说为啥要线程

如何创建线程:

from threading import Thread
def f1(n):
    print(n)
if __name__ == '__main__':
    t = Thread(target=f1,args=(1,))
    t.start()
 1 from threading import Thread
 2 class  Mythread(Thread):
 3     def __init__(self,n):
 4         super().__init__()  #必须写super()
 5         self.n = n  #接收参数
 6     def run(self):
 7         print(self.n)
 8 if __name__ == '__main__':
 9     t = Mythread(5,)
10     t.start()
继承类来创建线程

如何查看线程的进程id:

 1 import os
 2 from threading import Thread
 3 def f1(n):
 4     print("1号线程",os.getpid())
 5     print("%s号线程任务"%n)
 6 def f2(n):
 7     print("2号",os.getpid())
 8     print("%s号线程任务"%n)
 9 if __name__ == '__main__':
10     t1 = Thread(target=f1,args=(1,))
11     t2 = Thread(target=f2,args=(2,))
12     t1.start()
13     t2.start()
14     print("主线程",os.getpid()) 
15     print("主线程")
线程的id就是进程的id

进程和里面的线程存在数据共享:

 1 #通过对全局变量的修改,来验证进程和里面的线程之间,存在数据共享
 2 from threading import Thread
 3 import time
 4 num = 100
 5 def f1(n):
 6     time.sleep(2)
 7     global num
 8     num = num - n
 9     print("子线程的num",num)
10 if __name__ == '__main__':
11     t = Thread(target=f1,args=(1,))
12     t.start()
13     t.join() #由于线程里面存在时间延迟,所以加阻塞;否则不用阻塞,因为线程的开启比进程快
14     print("主线程中的num:",num)
15 # 打印结果:
16 # 子线程的num 99
17 # 主线程中的num: 99
通过对全局变量的修改,来验证

线程和进程速度对比:

 1 from threading import Thread
 2 from multiprocessing import Process
 3 import time
 4 def work():
 5     print('hello')
 6 if __name__ == '__main__':
 7     s1 = time.time()
 8     #在主进程下开启线程
 9     t=Thread(target=work)
10     t.start()
11     t.join()
12     t1 = time.time() - s1
13     print('进程的执行时间:',t1)
14     print('主线程/主进程')
15     s2 = time.time()
16     #在主进程下开启子进程
17     t=Process(target=work)
18     t.start()
19     t.join()
20     t2 = time.time() - s2
21     print('线程的执行时间:', t2)
22     print('主线程/主进程')
23 打印结果:
24 hello
25 进程的执行时间: 0.0009982585906982422
26 主线程/主进程
27 hello
28 线程的执行时间: 0.11446070671081543
29 主线程/主进程
效率对比

二、锁同步锁互斥锁

线程的锁跟进程的锁写法和用法一样.
注意:
    线程抢的是GIL锁,GIL锁相当于执行权限,拿到执行权限后才能拿到互斥锁Lock,其他线程也可以抢到GIL,但如果发现Lock仍然没有被释放则阻塞,即便是拿到执行权限GIL也要立刻交出来
 1 import time
 2 from threading import Lock,Thread
 3 num = 100
 4 def f1(lcok):
 5     global num
 6     lock.acquire()
 7     tmp = num
 8     tmp -=1
 9     time.sleep(0.0001)
10     num = tmp
11     lock.release()
12 if __name__ == '__main__':
13     t_list = []
14     lock = Lock()
15     for i in range(10):
16         t =Thread(target=f1,args=(lock,))
17         t.start()
18         t_list.append(t)
19     [tt.join() for tt in t_list]  #线程里有时间阻塞,所以加join
20     print("主线程执行完了",num)
与进程锁一致

三、死锁现象

什么是死锁现象:
  出现在,锁嵌套的时候,双方互相抢对方已经拿到的锁,导致锁双方互相等待,无法程序一致阻塞 一把锁只能同时由一个进程使用,只有释放(解锁)后,才能被别的程序用这把锁,否则就回出现死锁现象.另一个程序在等别的程序释放该锁,
 1 import time
 2 from threading import Thread, Lock
 3 def f1(locA, locB):
 4     locA.acquire()
 5     print('f1>>1号抢到了A锁')
 6     time.sleep(1)
 7     locB.acquire()
 8     print('f1>>1号抢到了B锁')
 9     locB.release()
10     locA.release()
11 def f2(locA, locB):
12     print('22222')
13     time.sleep(0.1) #这里写个延迟,确保f1先抢到锁,用来演示死锁现象
14     locB.acquire()
15     print('f2>>2号抢到了B锁')
16     locA.acquire()
17     time.sleep(1)
18     print('f2>>2号抢到了A锁')
19     locA.release()
20     locB.release()
21 if __name__ == '__main__':
22     locA = Lock()
23     locB = Lock()
24     t1 = Thread(target=f1, args=(locA, locB))
25     t2 = Thread(target=f2, args=(locA, locB))
26     t1.start()
27     t2.start()
28 # 打印结果:
29 # f1>>1号抢到了A锁
30 # 22222
31 # f2>>2号抢到了B锁
演示死锁现象

四、RLock(递归锁)

什么是递归锁:
    支持在同一线程中多次请求同一资源.
    这个RLock内部维护着一个Lock和一个counter变量,counter记录了acquire的次数,从而使得资源可以被多次require。直到一个线程所有的acquire都被release,
其他的线程才能获得资源
 1 import time
 2 from threading import Thread, Lock, RLock
 3 def f1(locA, locB):
 4     locA.acquire()
 5     print('f1>>1号抢到了A锁')
 6     time.sleep(1)
 7     locB.acquire()
 8     print('f1>>1号抢到了B锁')
 9     locB.release()
10     locA.release()
11 def f2(locA, locB):
12     print('22222')
13     time.sleep(0.1)
14     locB.acquire()
15     print('f2>>2号抢到了B锁')
16     locA.acquire()
17     time.sleep(1)
18     print('f2>>2号抢到了A锁')
19     locA.release()
20     locB.release()
21 if __name__ == '__main__':
22     locA= locB= RLock()  #递归锁,维护一个计数器,acquire一次就加1,release就减1
23     t1 = Thread(target=f1, args=(locA, locB)) #传进去2把锁
24     t2 = Thread(target=f2, args=(locA, locB)) 
25     t1.start()
26     t2.start()
演示递归锁

五、守护线程

什么是守护线程:
    守护线程会,会因为所有非守护线程的运行结束,而被终止
什么时守护进程:
    主进程代码运行完毕结束,守护进程就随之结束
import time
from threading import Thread
def f1():
    print("1号守护线程")
def f2():
    time.sleep(1)
    print("2号线程")
if __name__ == '__main__': #线程不用写main,写了也没事
    t1 = Thread(target = f1,)
    t2 = Thread(target = f2,)
    # 守护线程也一样,必须写在start之前
    # 因为守护线程里没有阻塞,运行效率又快,所以守护线程有机会输出
    t1.daemon = True
    t1.start()
    t2.start()
    print("主线程结束了.")

 六、关于线程的GIL锁

根据计算的数据不同,分为两种现象:
# io(阻塞)密集型时,线程则比进程短
# 计算密集型时,线程比进程所用的时间长(后面老师讲..锁的时候回解释)
什么是GIL锁:
    解释器同一时间只允许一个线程进入(串行).原因是python解释器执行线程的时候,解释器上有一个GIL锁(作者所处的时代,计算机cpu为单核,所以加了GIL锁.也可能有其他原因...)
线程计算密集型数据时,时间过长问题:
    用多进程去运行解决,多进程可以运用多核技术.但是要注意把代码上锁(Lock),防止数据的不安全问题.
补充一点:
    解释器面对多线程的时候是串行执行的,但是遇到IO密集型的时候,效率并不会因为串行而影响,原因是线程的执行速度很快,并且解释器,遇到IO就换下一个线程,IO结束就再回来继续执行(必须将代码上锁,不然不会等你执行完再给其他线程用),
这样并不怎么影响效率.
但是遇到计算密集型的数据时,就没有办法了,只有开启多进程来解决.

 七、信号量

同进程的一样
  Semaphore管理一个内置的计数器,
  每当调用acquire()时内置计数器-1;
  调用release() 时内置计数器+1;
  计数器不能小于0;当计数器为0时,acquire()将阻塞线程直到其他线程调用release()
from threading import Thread,Semaphore
import threading
import time
def func():
    sm.acquire()
    print('%s get sm' %threading.current_thread().getName())
    time.sleep(3)
    sm.release()
if __name__ == '__main__':
    sm=Semaphore(5) #同一个进程间的数据是共享的,所以不需要给线程传参
    for i in range(10):
        t=Thread(target=func)
        t.start()

 八、事件

同进程的一样
  线程的一个关键特性是每个线程都是独立运行且状态不可预测。如果程序中的其他线程需要通过判断某个线程的状态来确定自己下一步的操作,这时线程同步问题就会变得非常棘手。
为了解决这些问题,我们需要使用threading库中的Event对象。 对象包含一个可由线程设置的信号标志,它允许线程等待某些事件的发生。在初始情况下,Event对象中的信号标志被设置为假
参数介绍:
event.isSet():返回event的状态值;
event.wait():如果 event.isSet()==False将阻塞线程;
event.set(): 设置event的状态值为True,所有阻塞池的线程激活进入就绪状态, 等待操作系统调度;
event.clear():恢复event的状态值为False。
解释器
原文地址:https://www.cnblogs.com/lgw1171435560/p/10254540.html