python基础之多线程锁机制

GIL(全局解释器锁)

GIL并不是Python的特性,它是在实现Python解析器(CPython)时所引入的一个概念,是为了实现不同线程对共享资源访问的互斥,才引入了GIL

在Cpython解释器中,同一个进程下开启的多线程,同一时刻只能有一个线程执行,无法利用多核优势

python对于计算密集型的任务开多线程的效率甚至不如串行(没有大量切换),但是,对于IO密集型的任务效率还是有显著提升的。

GIL原理图

 

计算密集型:结果肯定是100,因为每一次start结果就已经出来了,所以第二个线程肯定是通过调用第一个线程的count值进行计算的

 1 def sub():
 2     global count
 3 
 4     '''线程的公共数据  下'''
 5     temp=count
 6     count=temp+1
 7     '''线程的公共数据  上'''
 8 
 9     time.sleep(2)
10 count=0
11 
12 l=[]
13 for i in range(100):
14     t=threading.Thread(target=sub,args=())
15     t.start()  #每一次线程激活,申请一次gillock
16     l.append(t)
17 for t in l:
18     t.join()
19 print(count)

 

io密集型:当第一个线程开始start的时候,由于sleep了0.001秒,这0.001秒对于人而言很短,但是对于cpu而言,这0.001秒已经做了很多的事情了,在这里cpu做的事情就是或许已经start了100个线程,所以导致大多数的线程调用的count值还是0,即temp=0,只有少数的线程完成了count=temp+1的操作,所以输出的count结果不确定,可能是7、8、9,也可能是10几。

 1 def sub():
 2     global count
 3 
 4     '''线程的公共数据  下'''
 5     temp=count
 6     time.sleep(0.001)    #大量的io操作
 7     count=temp+1
 8     '''线程的公共数据  上'''
 9 
10     time.sleep(2)
11 count=0
12 
13 l=[]
14 for i in range(100):
15     t=threading.Thread(target=sub,args=())
16     t.start()
17     l.append(t)
18 for t in l:
19     t.join()
20 print(count)

 

注意以下的锁都是多线程提供的锁机制,与python解释器引入的gil概念无关

互斥锁(同步锁)

互斥锁是用来解决上述的io密集型场景产生的计算错误,即目的是为了保护共享的数据,同一时间只能有一个线程来修改共享的数据。

 1 def sub():
 2     global count
 3     lock.acquire()  #上锁,第一个线程如果申请到锁,会在执行公共数据的过程中持续阻塞后续线程
 4                     #即后续第二个或其他线程依次来了发现已经被上锁,只能等待第一个线程释放锁
 5                     #当第一个线程将锁释放,后续的线程会进行争抢
 6 
 7     '''线程的公共数据  下'''
 8     temp=count
 9     time.sleep(0.001)
10     count=temp+1
11     '''线程的公共数据  上'''
12 
13     lock.release()  #释放锁
14     time.sleep(2)
15 count=0
16 
17 l=[]
18 lock=threading.Lock()   #将锁内的代码串行化
19 for i in range(100):
20     t=threading.Thread(target=sub,args=())
21     t.start()
22     l.append(t)
23 for t in l:
24     t.join()
25 print(count)

 

死锁

保护不同的数据就应该加不同的锁。

所以当有多个互斥锁存在的时候,可能会导致死锁,死锁原理如下:

 1 import threading
 2 import time
 3 def foo():
 4     lockA.acquire()
 5     print('func foo ClockA lock')
 6     lockB.acquire()
 7     print('func foo ClockB lock')
 8     lockB.release()
 9     lockA.release()
10 
11 def bar():
12 
13     lockB.acquire()
14     print('func bar ClockB lock')
15     time.sleep(2)  # 模拟io或者其他操作,第一个线程执行到这,在这个时候,lockA会被第二个进程占用
16                    # 所以第一个进程无法进行后续操作,只能等待lockA锁的释放
17     lockA.acquire()
18     print('func bar ClockA lock')
19     lockB.release()
20     lockA.release()
21 
22 def run():
23     foo()
24     bar()
25 
26 lockA=threading.Lock()
27 lockB=threading.Lock()
28 for i in range(10):
29     t=threading.Thread(target=run,args=())
30     t.start()
31 
32 输出结果:只有四行,因为产生了死锁阻断了
33 func foo ClockA lock
34 func foo ClockB lock
35 func bar ClockB lock
36 func foo ClockA lock

 

 

递归锁(重要)

解决死锁

 1 import threading
 2 import time
 3 def foo():
 4     rlock.acquire()
 5     print('func foo ClockA lock')
 6     rlock.acquire()
 7     print('func foo ClockB lock')
 8     rlock.release()
 9     rlock.release()
10 
11 def bar():
12     rlock.acquire()
13     print('func bar ClockB lock')
14     time.sleep(2)
15     rlock.acquire()
16     print('func bar ClockA lock')
17     rlock.release()
18     rlock.release()
19 
20 
21 def run():
22     foo()
23     bar()
24 
25 rlock=threading.RLock() #RLock本身有一个计数器,如果碰到acquire,那么计数器+1
26                         #如果计数器大于0,那么其他线程无法查收,如果碰到release,计数器-1
27 
28 for i in range(10):
29     t=threading.Thread(target=run,args=())
30     t.start()

 

 

Semaphore(信号量)

实际上也是一种锁,该锁用于限制线程的并发量

以下代码在sleep两秒后会打印出100个ok

1 import threading
2 import time
3 def foo():
4     time.sleep(2)
5     print('ok')
6 
7 for i in range(100):
8     t=threading.Thread(target=foo,args=())
9     t.start()

 

每2秒打印5次ok

 1 import threading
 2 import time
 3 sem=threading.Semaphore(5)
 4 def foo():
 5     sem.acquire()
 6     time.sleep(2)
 7     print('ok')
 8     sem.release()
 9 
10 for i in range(100):
11     t=threading.Thread(target=foo,args=())
12     t.start()
posted @ 2017-07-26 08:51  李大根er  阅读(18949)  评论(0编辑  收藏  举报