线程中的锁的相关问题
一、GIL全局解释器锁
python解释器有很多种,最常见的就是Cpython解释器。
GIL的存在是因为CPython解释器的线程的内存管理不是安全的。GIL本质也是一把互斥锁,将并发变成串行牺牲效率保证数据的安全,用来阻止同一个进程下的多个线程的同时执行(同一个进程内多个线程无法实现并行但是可以实现并发)。
所谓的内存管理也就是python中存在的垃圾回收机制,引用计数、标记清除以及分带回收,当线程中的某些变量不存在绑定关系的时候,会被垃圾回收机制清除。
每个线程的运行是需要解释器将程序解释成计算机能读懂的语言,每个进程中都有一个解释器,若没有GIL,每个线程都能同时拿到解释器,并对同一个数据进行处理,例如每个线程(没有阻塞的线程)将拿到的数据减一,如果原数据是100,100个线程都减一应得到的结果是0,但实际上得到的是99,此时就会表现出数据的处理是不安全的。
1 from threading import Thread 2 import time 3 4 n = 100 5 6 7 def task(): 8 global n 9 tmp = n 10 n = tmp - 1 11 12 13 t_list = [] 14 for i in range(100): 15 t = Thread(target=task) 16 t.start() 17 t_list.append(t) 18 19 for t in t_list: 20 t.join() 21 22 print(n) # 0
当线程遇到阻塞的时候,会自动释放全局解释器锁,其它的线程就会抢到该锁。(time.sleep(1))在每个线程阻塞的过程中,每个线程拿到数据可能时一样的。当阻塞结束后,每个线程对想通的数据做相同的操作时,最终得到的结果可能与预期不同对可能发生抢数据的行为应对该数据加锁。
from threading import Thread import time n = 100 def task(): global n tmp = n time.sleep(1) n = tmp - 1 t_list = [] for i in range(100): t = Thread(target=task) t.start() t_list.append(t) for t in t_list: t.join() print(n) # 99
二、死锁
所谓死锁,就是多个线程需要多个锁,两个或多个线程抢到了一个或多个其它线程需要的锁,结果导致每个线程都在等待其它线程释放锁,最终形成了死锁。
1 from threading import Thread,Lock,current_thread,RLock 2 import time 3 4 mutexA = Lock() 5 mutexB = Lock() 6 7 class MyThread(Thread): 8 def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 9 self.func1() 10 self.func2() 11 12 def func1(self): 13 mutexA.acquire() 14 print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name 15 mutexB.acquire() 16 print('%s抢到了B锁'%self.name) 17 mutexB.release() 18 print('%s释放了B锁'%self.name) 19 mutexA.release() 20 print('%s释放了A锁'%self.name) 21 22 def func2(self): 23 mutexB.acquire() 24 print('%s抢到了B锁'%self.name) 25 time.sleep(1) 26 mutexA.acquire() 27 print('%s抢到了A锁' % self.name) 28 mutexA.release() 29 print('%s释放了A锁' % self.name) 30 mutexB.release() 31 print('%s释放了B锁' % self.name) 32 33 for i in range(10): 34 t = MyThread() 35 t.start()
上述图片的结果最终阻塞在了线程2抢到了A锁,线程1抢到了B锁。线程刚创建完成的时候线程1抢到了A锁,其它线程也要抢A锁,由于线程1没有释放A锁,所以其它线程阻塞在抢A锁,线程1继续运行,拿到了B锁,然后释放了B锁,再释放了A锁,释放A锁的一瞬间,线程2抢到了A锁,线程1继续运行去拿B锁,此时没有线程抢B锁,由于线程2抢到了A锁,其它线程继续阻塞再抢A锁处,线程1、2继续运行,此时线程2要B锁,线程1要A锁,然而两个线程都没有释放对方需要的锁,所以程序阻塞在了此处。这就是死锁现象。
有一种锁叫递归锁,该锁的使用机制就是同一个线程可以在当前运行的过程中不间断的去拿该锁,内部会自动计数加1,每释放一次就会减1,当计数为0时,其它的线程就可以使用该锁。在以上程序的基础上,让A锁和B锁由同一个递归锁制造,可以解决死锁现象。
1 from threading import Thread,Lock,current_thread,RLock 2 import time 3 4 5 mutexA = mutexB = RLock() # A B现在是同一把锁 6 7 8 class MyThread(Thread): 9 def run(self): # 创建线程自动触发run方法 run方法内调用func1 func2相当于也是自动触发 10 self.func1() 11 self.func2() 12 13 def func1(self): 14 mutexA.acquire() 15 print('%s抢到了A锁'%self.name) # self.name等价于current_thread().name 16 mutexB.acquire() 17 print('%s抢到了B锁'%self.name) 18 mutexB.release() 19 print('%s释放了B锁'%self.name) 20 mutexA.release() 21 print('%s释放了A锁'%self.name) 22 23 def func2(self): 24 mutexB.acquire() 25 print('%s抢到了B锁'%self.name) 26 time.sleep(1) 27 mutexA.acquire() 28 print('%s抢到了A锁' % self.name) 29 mutexA.release() 30 print('%s释放了A锁' % self.name) 31 mutexB.release() 32 print('%s释放了B锁' % self.name) 33 34 for i in range(10): 35 t = MyThread() 36 t.start()
但是此时的结果是当每个线程执行完毕后,下一个线程才能使用递归锁。
三、信号量
信号量在不同的领域由不同的使用方法和定义
在线程中的信号量可以理解为同一时间允许规定的个数的线程产生的规定数量的数据。
1 from threading import Semaphore,Thread 2 import time 3 4 5 sm = Semaphore(5) # 造了一个含有五个的坑位的公共厕所 6 7 def task(name): 8 print(name) 9 sm.acquire() 10 print('%s占了一个坑位'%name) 11 time.sleep(1) 12 sm.release() 13 14 for i in range(40): 15 t = Thread(target=task,args=(i,)) 16 t.start()
每个线程中的打印坑位的数据同一时间允许五个线程执行,在此之前的打印操作每个进程都可以执行。
四、event事件
event事件的出现就是为了实现一个线程等待另一个线程产生特定的信号是执行相应的步骤。例如一个线程模拟红灯到绿灯的过程,另一个线程模拟汽车红灯停绿灯行的过程,显然后者需要等待前者发出变绿灯的信号时才能启动汽车。
1 from threading import Event,Thread 2 import time 3 4 # 先生成一个event对象 5 e = Event() 6 7 8 def light(): 9 print('红灯正亮着') 10 time.sleep(3) 11 e.set() # 发信号 12 print('绿灯亮了') 13 14 def car(name): 15 print('%s正在等红灯'%name) 16 e.wait() # 等待信号 17 print('%s加油门飙车了'%name) 18 19 t = Thread(target=light) 20 t.start() 21 22 for i in range(10): 23 t = Thread(target=car,args=('伞兵%s'%i,)) 24 t.start()
五、线程队列
队列的本质时管道加锁的操作,在使用队列的时候就避免了自己给数据加锁的操作,不会出现死锁的现象
可以使用的队列有Queue、LifoQueue以及PriorityQueue,分别为普通队列、后进先出队列以及优先级队列。其中优先级队列于进入的顺序无关,只与设置的级别有关,取值的时候从优先级低的开始取值。
1 q = queue.Queue() 2 q.put('hahha') 3 print(q.get()) # hahha 4 5 6 q = queue.LifoQueue() 7 q.put(1) 8 q.put(2) 9 q.put(3) 10 print(q.get()) # 3 11 12 13 q = queue.PriorityQueue() 14 # 数字越小 优先级越高 15 q.put((10,'haha'),block=False) 16 q.put((100,'hehehe')) 17 q.put((0,'xxxx')) 18 q.put((-10,'yyyy')) 19 print(q.get()) # (-10,'yyyy')
posted on 2019-08-14 17:59 so_interesting 阅读(460) 评论(0) 编辑 收藏 举报