day21线程锁
线程锁(互斥锁Mutex)
一个进程下可以启动多个线程,多个线程共享父进程的内存空间,也就意味着每个线程可以访问同一份数据,此时,如果2个线程同时要修改同一份数据,会出现什么状况?
1.import time
2.import threading
3.
4.def addNum():
5. global num #在每个线程中都获取这个全局变量
6. print('--get num:',num )
7. time.sleep(1)
8. num -=1 #对此公共变量进行-1操作
9.
10.num = 100 #设定一个共享变量
11.thread_list = []
12.for i in range(100):
13. t = threading.Thread(target=addNum)
14. t.start()
15. thread_list.append(t)
16.
17.for t in thread_list: #等待所有线程执行完毕
18. t.join()
19.
20.
21.print('final num:', num )
正常来讲,这个num结果应该是0, 但在python 2.7上多运行几次,会发现,最后打印出来的num结果不总是0,为什么每次运行的结果不一样呢? 哈,很简单,假设你有A,B两个线程,此时都 要对num 进行减1操作, 由于2个线程是并发同时运行的,所以2个线程很有可能同时拿走了num=100这个初始变量交给cpu去运算,当A线程去处完的结果是99,但此时B线程运算完的结果也是99,两个线程同时CPU运算的结果再赋值给num变量后,结果就都是99。那怎么办呢? 很简单,每个线程在要修改公共数据时,为了避免自己在还没改完的时候别人也来修改此数据,可以给这个数据加一把锁, 这样其它线程想修改此数据时就必须等待你修改完毕并把锁释放掉后才能再访问此数据。
注:
不要在3.x上运行,不知为什么,3.x上的结果总是正确的,可能是自动加了锁
加锁版本
1.import time
2.import threading
3.
4.def addNum():
5. global num #在每个线程中都获取这个全局变量
6. print('--get num:',num )
7. time.sleep(1)
8. lock.acquire() #修改数据前加锁
9. num -=1 #对此公共变量进行-1操作
10. lock.release() #修改后释放
11.
12.num = 100 #设定一个共享变量
13.thread_list = []
14.lock = threading.Lock() #生成全局锁
15.for i in range(100):
16. t = threading.Thread(target=addNum)
17. t.start()
18. thread_list.append(t)
19.
20.for t in thread_list: #等待所有线程执行完毕
21. t.join()
22.
23.print('final num:', num )
在上面的代码加了锁;程序会变成串行的;怎样测试那?
在释放锁 release() 之前加 sleep() 控制其等待时间
注意:
在实际的操作中我们要避免这样操作;所有要严格控制;不要加 sleep让其变成串行(加了有点;站着茅坑不拉屎的感觉)
GIL VS Lock
机智的同学可能会问到这个问题,就是既然你之前说过了,Python已经有一个GIL来保证同一时间只能有一个线程来执行了,为什么这里还需要lock? 注意啦,这里的lock是用户级的lock,跟那个GIL没关系 ,具体我们通过下图来看一下+配合我现场讲给大家,就明白了。
RLock(递归锁)
说白了就是在一个大锁中还要再包含子锁
1.import threading,time
2.
3.def run1():
4. print("grab the first part data")
5. lock.acquire()
6. global num
7. num +=1
8. lock.release()
9. return num
10.def run2():
11. print("grab the second part data")
12. lock.acquire()
13. global num2
14. num2+=1
15. lock.release()
16. return num2
17.def run3():
18. lock.acquire()
19. res = run1()
20. print('--------between run1 and run2-----')
21. res2 = run2()
22. lock.release()
23. print(res,res2)
24.
25.
26.if __name__ == '__main__':
27.
28. num,num2 = 0,0
29. lock = threading.Lock()
30. for i in range(10):
31. t = threading.Thread(target=run3)
32. t.start()
33.
34.while threading.active_count() != 1:
35. print(threading.active_count())
36.else:
37. print('----all threads done---')
38. print(num,num2)
结果;无限的循环打印进程个数;程了一个死循环;锁死的状态
分析:
我们可以这样理解上面的代码;你进学校大门;然后在进里面的2个小房间的过程。首先你打开锁进大门;然后锁上大门;
走到小房间;打开锁,进入房间出来锁上小房间门
再通过学校到另一个房间,开锁,进入,出来锁门
注意:锁多了;我们弄混了;你用大门的要是去开小房间的;结果打不开;这样钥匙不配锁;导致你被锁死再里面;上面的代码;就是这样的结果
问题:怎么解决那?
使用递归锁
1.import threading,time
2.
3.def run1():
4. print("grab the first part data")
5. lock.acquire()
6. global num
7. num +=1
8. lock.release()
9. return num
10.def run2():
11. print("grab the second part data")
12. lock.acquire()
13. global num2
14. num2+=1
15. lock.release()
16. return num2
17.def run3():
18. lock.acquire()
19. res = run1()
20. print('--------between run1 and run2-----')
21. res2 = run2()
22. lock.release()
23. print(res,res2)
24.
25.
26.if __name__ == '__main__':
27.
28. num,num2 = 0,0
29. lock = threading.RLock()
30. for i in range(10):
31. t = threading.Thread(target=run3)
32. t.start()
33.
34.while threading.active_count() != 1:
35. print(threading.active_count())
36.else:
37. print('----all threads done---')
38. print(num,num2)
总结:
上面的2段代码的区别;之前创建的是普通锁;
lock = threading.Lock()
第二创建的是递归锁
lock = threading.RLock()
递归锁的本质:
利用了类似字典的形式
1.locks = {
2. dodr1:key1
3. door2:key2
4.}
像这样;将对用的门和锁设置好;解锁的时候;也是去取对应的钥匙
Semaphore(信号量)
互斥锁 同时只允许一个线程更改数据,而Semaphore是同时允许一定数量的线程更改数据 ,比如厕所有3个坑,那最多只允许3个人上厕所,后面的人只能等里面有人出来了才能再进去。与前面的锁的区别就是;一个门同时有了多把锁
1.import threading,time
2.
3.def run(n):
4. semaphore.acquire()
5. time.sleep(1)
6. print("run the thread: %s\n" %n)
7. semaphore.release()
8.
9.if __name__ == '__main__':
10.
11. semaphore = threading.BoundedSemaphore(5) #最多允许5个线程同时运行
12. for i in range(20):
13. t = threading.Thread(target=run,args=(i,))
14. t.start()
15.
16.while threading.active_count() != 1:
17. pass #print threading.active_count()
18.else:
19. print('----all threads done---')
分析:
实例化信息量的时候;后面括号接收数字;表示最多允许同时运行的线程
semaphore = threading.BoundedSemaphore(5) #最多允许5个线程同时运行
浙公网安备 33010602011771号