多线程

一、GIL全局解释器锁

1.什么是GIL

官方给出的解释是:在CPython中,这个全局解释器锁,也成为GIL,是一个互斥锁,防止多个线程在同一时间执行python字节码,这个锁是非常重要的,因为CPython的内存管理非线程安全的,很多其他的特性依赖于GIL,所以即使它影响了程序效率也无法将其直接去除

★★★总结:在CPython中,GIL会把线程的并行变成串行,导致效率降低。

需要知道的是,解释器并不只有CPython,还有PyPy,JPython等等。GIL也仅仅存在于CPython中,这并不是Python这门语言的问题,而是CPython解释器的问题!

2.GIL的加锁与解锁时机

加锁的时机:在调用解释器时立即加锁

解锁时机:

♦ 当前线程遇到了IO时释放

♦ 当前线程执行时间超过设定值的释放

3.GIL性能讨论

GIL的优点:

    保证了CPython中的内存管理是线程安全的

GIL的缺点:

  互斥锁的特性使得多线程无法并行

直接上代码测试:

1.计算密集型

 1 from multiprocessing import Process
 2 from threading import Thread
 3 import os,time
 4 def work():
 5     res=0
 6     for i in range(100000000):
 7         res*=i
 8 
 9 
10 if __name__ == '__main__':
11     l=[]
12     print(os.cpu_count())  # 本机为6核
13     start=time.time()
14     for i in range(6):
15         p=Process(target=work) #耗时  4.732933044433594
16         p=Thread(target=work) #耗时 22.83087730407715
17         l.append(p)
18         p.start()
19     for p in l:
20         p.join()
21     stop=time.time()
22     print('run time is %s' %(stop-start))

  2.IO密集型

 1 from multiprocessing import Process
 2 from threading import Thread
 3 import threading
 4 import os,time
 5 def work():
 6     time.sleep(2)
 7 
 8 
 9 if __name__ == '__main__':
10     l=[]
11     print(os.cpu_count()) #本机为6核
12     start=time.time()
13     for i in range(4000):
14         p=Process(target=work) #耗时9.001083612442017s多,大部分时间耗费在创建进程上
15         # p=Thread(target=work) #耗时2.051966667175293s多
16         l.append(p)
17         p.start()
18     for p in l:
19         p.join()
20     stop=time.time()
21     print('run time is %s' %(stop-start))

  ★★总结:由上得到,当执行计算密集型的代码时,多进程的运行速度要比多线程快。而当处理IO密集型的代码时,多线程的运行速度要快于多进程的。

二、GIL与普通的互斥锁

 1 from threading import Thread,Lock
 2 import time
 3 
 4 a = 0
 5 def task():
 6     global a
 7     temp = a
 8     time.sleep(0.01) 
 9     a = temp + 1
10 
11 t1 = Thread(target=task)
12 t2 = Thread(target=task)
13 t1.start()
14 t2.start()
15 t1.join()
16 t2.join()
17 print(a)
加锁前

过程分析:

1.线程1获得CPU执行权,并获取GIL锁执行代码,得到a的值为0后进入睡眠,释放CPU并释放GIL

2.线程2获得CPU执行权,并获取GIL锁执行代码,得到a的值为0后进入睡眠,释放CPU并释放GIL

3.线程1睡醒后获得CPU执行权,并获取GIL执行代码,将temp的值0+1后赋给a,执行完毕释放CPU并释放GIL

4.线程2睡醒后获得CPU执行权,并获取GIL执行代码,将temp的值0+1后赋给a,执行完毕释放CPU并释放GIL,最后a的值也就是1

之所以出现问题就是因为两个线程在并发的执行用一段代码,解决方案就是加锁。

 1 from threading import Thread,Lock
 2 import time
 3 
 4 lock = Lock()
 5 a = 0
 6 def task():
 7     global a
 8     lock.acquire()
 9     temp = a
10     time.sleep(0.01)
11     a = temp + 1
12     lock.release()
13 
14 t1 = Thread(target=task)
15 t2 = Thread(target=task)
16 t1.start()
17 t2.start()
18 t1.join()
19 t2.join()
20 print(a)
加锁后

过程分析:

1.线程1获得CPU执行权,并获取GIL锁执行代码,得到a的值为0后进入睡眠,释放CPU并释放GIL,不释放lock

2.线程2获得CPU执行权,并获取GIL锁,尝试或lock失败,无法执行,释放CPU并释放GIL

3.线程1睡醒后获得CPU执行,并获取GIL继续执行代码,将temp的值0+1后赋给a,执行完毕释放CPU释放GIL,释放lock,此时a的值为1

4.线程2获得CPU执行权,获取GIL锁,尝试获取lock成功,执行代码,得到a的值为1后进入睡眠,释放CPU并释放lock

5.线程2睡醒后获得CPU执行权,获取GIL继续执行代码,将temp的值1+1赋值给a,执行完毕释放CPU释放GIL,释放lock,此时a的值为2

三、死锁

 1 from threading import Thread,Lock,current_thread
 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
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()
死锁现象

  死锁原因:线程1执行func1,首先抢到了A锁,随后抢到了B锁,然后释放了B锁,最后释放了A锁。接着执行线程2,抢到了B锁,这时遇到了IO操作,中途睡了1秒,在这时间内线程2开始执行func1,首先抢到了A锁,但是紧接着在抢B锁时,失败了。因为此时A锁还被线程1抢着,1秒后线程1睡醒了,要接着抢A锁了,但此时A锁被线程2抢占了,线程2只有抢到B锁,才能继续往下走,而线程1必须抢到A锁才能继续执行下一步。那么此时就陷入了死锁状态。

四、递归锁

 1 from threading import Thread,RLock,current_thread
 2 import time
 3 
 4 mutexA = mutexB = RLock() # A B现在是同一把锁
 5 
 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
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()
递归锁

   ▶ Rlock可以被第一个抢到锁的人连续的acquire和release

▶ 每acquire一次锁身上的计数加1

▶ 每release一次锁身上的计数减1

▶ 只要锁的计数不为0 其他人都不能抢

五、信号量

 1 from threading import Semaphore,Thread
 2 import time
 3 import random
 4 
 5 sm = Semaphore(5) # 就好比制造了一个五个坑位的厕所
 6 
 7 def task(name):
 8     sm.acquire()
 9     print('%s占了一个位子'%name)
10     time.sleep(1)
11     sm.release()
12 
13 for i in range(40):
14     t = Thread(target=task,args=(i,))
15     t.start()
信号量

  与递归锁的不同点:递归锁只允许一个线程抢占,而信号量可以指定抢占的数量

六、event事件

 1 from threading import Thread,Event
 2 import time
 3 
 4 #先生成一个对象
 5 e = Event()
 6 n = 3
 7 def light():
 8     global n
 9     print('红灯正亮着')
10     for i in range(3):
11         time.sleep(1)
12         print('\r%s'%n,end='')
13         n -= 1
14     time.sleep(1)
15     e.set() #发信号
16     print('\rGo!\r')
17 
18 def car(name):
19     print('%s正在烧胎!'%name)
20     e.wait() #等待信号
21     print('%s弹射起步!'%name)
22 
23 t = Thread(target=light)
24 t.start()
25 
26 for i in range(1,11):
27     t = Thread(target=car,args=('秋名山车神%s'%i,))
28     t.start()
event飙车

七、线程queue

首先带着一个疑问,同一个线程中多个线程本身就可以共享数据,为什么还还要用到队列?

因为队列是管道+锁,使用队列的话你就不需要自己动手操作锁的问题,因为锁操作的不好极容易产生死锁现象

1 import queue
2 
3 q = queue.Queue()
4 q.put('heiheihei')
5 print(q.get())
Queue
1 q = queue.LifoQueue()
2 q.put(1)
3 q.put(2)
4 q.put(3)
5 print(q.get()) #堆栈
LifoQueue
1 q = queue.PriorityQueue()
2 #数字越小,优先级就越高
3 q.put((10,'bibibi'))
4 q.put((100,'gugugu'))
5 q.put((50,'lalala'))
6 q.put((0,'xixixi'))
7 print(q.get())
PriorityQueue

 

posted @ 2019-08-14 20:06  朱朱朱朱朱  阅读(197)  评论(0编辑  收藏  举报