天天看点

线程同步导致的互斥,以及互斥锁解决资源竞争

很多时候我们需要线程同步,但线程同步也会引发一些问题。

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争抢,谁先抢到就会先执行谁,这样一直循环,谁最后执行谁就得到最终结果。

继续阅读