很多时候我们需要线程同步,但线程同步也会引发一些问题。
1.如果处理量小的话,还不会出现问题
1 import threading
2 import time
3
4 num=0
5 def test1(temp):
6 for i in range(temp):
7 global num
8 num+=1
9 print("-----test1----num:%s"%num)
10
11
12 def test2(temp):
13 for i in range(temp):
14 global num
15 num+=1
16 print("-----test1----num:%s"%num)
17
18
19 def main():
20 #target指定线程将来去哪个函数执行代码
21 #args必须是元组,指定调用函数时,传递的参数
22 t1=threading.Thread(target=test1,args=(100,))
23 t2=threading.Thread(target=test2,args=(100,))
24 t1.start()
25 time.sleep(1)
26
27 t2.start()
28 time.sleep(1)
29
30 print("----main----num:%s"%num)
31
32
33 if __name__=="__main__":
34 main()
执行结果:
-----test1----num:100
-----test1----num:200
----main----num:200
2.如果处理量大的话,就会出现问题
1 import threading
2 import time
3
4 num=0
5 def test1(temp):
6 for i in range(temp):
7 global num
8 num+=1
9 print("-----test1----num:%s"%num)
10
11
12 def test2(temp):
13 for i in range(temp):
14 global num
15 num+=1
16 print("-----test1----num:%s"%num)
17
18
19 def main():
20 #target指定线程将来去哪个函数执行代码
21 #args必须是元组,指定调用函数时,传递的参数
22 t1=threading.Thread(target=test1,args=(1000000,))
23 t2=threading.Thread(target=test2,args=(1000000,))
24 t1.start()
25
26
27 t2.start()
28 time.sleep(1)
29
30 print("----main----num:%s"%num)
31
32
33 if __name__=="__main__":
34 main()
执行结果:
-----test1----num:1576531
-----test2----num:1622410
----main----num:1622410
理论上我们想要的结果是test1是1000000,test1是2000000,main是2000000. 而结果却不一样,问题出在num+=1,num+=1实际上分为3步,
1.获取num,
2.num+1
3.重新赋值给num
当执行test1()时,执行了前2步之后,系统跑去执行test2(),test2()也执行了前2步之后,又回到test1(),继续接着上一步执行,这时把值1赋值给num,我们知道线程的执行次序是不固定的,test1()刚把1赋值给num,这时系统又去执行test2(),test2()该执行第3步了,把1赋值给num,这里就出现问题了,num的值被重复赋了2次同样的值,原本是2的,结果还是1,所以最终结果我们得到的值比预期要小,这就时进程同步造成的影响,解决这个问题,需要用到互斥锁。
threading模块中定义了Lock类,可以方便的处理锁定:
创建锁
mutex = threading.Lock()
锁定
mutex.acquire()
释放
mutex.release()
我们知道线程是共享全局变量的,所以互斥锁在哪创建都一样;互斥锁每次只能执行一个,执行完一个后才能执行下一个。
1 import threading
2 import time
3 mutex=threading.Lock()
4 num=0
5 def test1(temp):
6 mutex.acquire() #上锁,如果之前没有被上锁,那么此时上锁会成功;如果上锁之前已经被上锁,那么此时会被堵塞在这里,直到这个锁被解开为止
7 for i in range(temp):
8 global num
9 num+=1
10 mutex.release()
11 print("-----test1----num:%s"%num)
12
13
14 def test2(temp):
15 mutex.acquire()
16 for i in range(temp):
17 global num
18 num+=1
19 mutex.release()
20 print("-----test2----num:%s"%num)
21
22
23 def main():
24 #target指定线程将来去哪个函数执行代码
25 #args必须是元组,指定调用函数时,传递的参数
26 t1=threading.Thread(target=test1,args=(1000000,))
27 t2=threading.Thread(target=test2,args=(1000000,))
28 t1.start()
29
30 t2.start()
31 time.sleep(1)
32
33 print("----main----num:%s"%num)
34
35
36 if __name__=="__main__":
37 main()
执行结果:
-----test1----num:1000000
-----test2----num:2000000
----main----num:2000000
互斥锁改进
上锁有个原则,被上锁的代码量越小越好。比如上面一个例子,test1和test2两个线程执行顺序不固定,谁先运行就先执行谁,test1先抢到互斥锁,等test1执行完之后,再去执行线程test2,在每个线程里,我们实现互斥锁是对整个for循环而言,如果for循环执行1分钟,那么test2线程就得等100s,这就很不友好。我们就缩小上锁的代码,尽可能让各个线程在短时间内都能执行
1 import threading
2 import time
3 mutex=threading.Lock()
4 num=0
5 def test1(temp):
6 for i in range(temp):
7 mutex.acquire()
8 global num
9 num+=1
10 mutex.release()
11 print("-----test1----num:%s"%num)
12
13
14 def test2(temp):
15 for i in range(temp):
16 mutex.acquire()
17 global num
18 num+=1
19 mutex.release()
20 print("-----test2----num:%s"%num)
21
22
23 def main():
24 #target指定线程将来去哪个函数执行代码
25 #args必须是元组,指定调用函数时,传递的参数
26 t1=threading.Thread(target=test1,args=(1000000,))
27 t2=threading.Thread(target=test2,args=(1000000,))
28 t1.start()
29
30 t2.start()
31 time.sleep(1)
32
33 print("----main----num:%s"%num)
34
35
36 if __name__=="__main__":
37 main()
执行结果:
-----test1----num:1769299
-----test2----num:2000000
----main----num:2000000
出现这种情况是因为,当执行到 t1.start(),t2.start()时,test1和test2会抢先执行,如果test1先执行,test2就得等待,test1先执行一次num+1,互斥锁释放后,又会遇到acquire,这时会跟test2的acquire争抢,谁先抢到就会先执行谁,这样一直循环,谁最后执行谁就得到最终结果。