并发编程(三) GIL锁和其他知识

1.全局解释器锁GIL

什么是GIL

  GIL是全局解释器锁他规定了每个线程在被CPU执行前都要获得一个GIL,并且同一时刻只有一个线程被执行

为什么要有GIL

  因为CPython解释器的内存管理不是线程安全的,所以在CPython中增加的一个GIL锁

线程释放GIL锁的情况

  在I/O操作等会引起阻塞状态的操作时,会暂时释放GIL,其他线程可以获取GIL继续执行

python的多线程和多进程的使用场景

  1.当CPU处于多任务计算密集型的情况下

    单核:两者差不多,但是多线程更加省资源

    多核:由于多线程不能利用多核的优势,所以使用多进程更有优势

  2.当CPU处于多任务I/O密集型的情况下

    单核:和上述一样,多线程更省资源

    多核:由于I/O密集型的任务耗费的时间大部分在I/O操作上,并且cpu在阻塞状态时可以暂时释放GIL,所以还是多线程更省资源(开启进程是耗费资源,当需要进行的任务过多,多进程耗费的时间会大大超过多线程)

 1 # 计算密集型
 2 from multiprocessing import Process
 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())  # 本机为8核
13     start=time.time()
14     for i in range(6):
15         p=Process(target=work)  # 在当前机器上,多进程耗时 9.134473323822021s
16         # p=Thread(target=work)  # 在当前机器上,多线程耗时 51.49368095397949s
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))
多核计算机计算密集型下多进程和多线程的区别
 1 from multiprocessing import Process
 2 import os,time
 3 def work():
 4     time.sleep(2)
 5 
 6 if __name__ == '__main__':
 7     l=[]
 8     print(os.cpu_count()) #本机为8核
 9     start=time.time()
10     for i in range(400):
11         p=Process(target=work)  # 在当前机器上,多进程耗时12.091996908187866s多,大部分时间耗费在创建进程上
12         # p=Thread(target=work)  # 在当前机器上,多线程耗时2.050579309463501s
13         l.append(p)
14         p.start()
15     for p in l:
16         p.join()
17     stop=time.time()
18     print('run time is %s' %(stop-start))
多核计算机I/O密集型下多进程和多线程的区别

由上述计算结果表明,一般情况下,只有当多核计算机在执行计算密集型操作时使用多进程,但是这不是一成不变的,要视具体情况而定,并且实际开发中我们都是多进程+多线程结合使用

 多线程I/O操作时不会耗费时间

 1 from threading import Thread
 2 import time
 3 
 4 n = 100
 5 
 6 def task():
 7     global n
 8     temp = n
 9     time.sleep(1)
10     '''
11     模拟I/O操作,当I/O进行时i,线程会暂时释放GIL,其他线程可以获得,他们都进行到这一步,n = 100
12     I/O操作结束,开始执行每个线程的n-1得到的都是99,所以结果是99
13     
14     如果没有I/O操作,那因为GIL锁的原因,每个线程都需要执行完才会释放GIL,
15     故第二个线程获得的n是第一个线程-1后的结果,所以结果是0
16     '''
17     n = temp - 1
18 
19 l = []
20 for i in range(100):
21     t = Thread(target=task)
22     t.start()
23     l.append(t)
24 for t in l:
25     t.join()
26 print(n)
GIL有无I/O操作时的区别

time模拟I/O操作,当I/O进行时i,线程会暂时释放GIL,其他线程可以获得,他们都进行到这一步,n = 100I/O操作结束,开始执行每个线程的n-1得到的都是99,所以结果是99

如果没有I/O操作,那因为GIL锁的原因,每个线程都需要执行完才会释放GIL,故第二个线程获得的n是第一个线程-1后的结果,所以结果是0

 

2.死锁

什么是死锁

  指的是两个或多个进程或线程在执行过程中,因争夺资源而造成了一种互相等待的问题,如果没有外力的作用,它们都无法执行下去,这时就是处于死锁状态

 1 from threading import Thread,Lock
 2 
 3 mutex = Lock()
 4 
 5 def task():
 6     mutex.acquire()
 7     print('hello world')
 8     mutex.acquire()  
 9     '''
10     想要获得这个锁,需要等到上面的人释放锁,但是他想要释放锁必须先获得锁,这就形成了死锁
11     '''
12     print('hi')
13     mutex.release()
14     mutex.release()
15 
16 t = Thread(target=task)
17 t.start()
死锁的小例子

线程1开始执行,time.sleep(1)模拟I/O操作,当第一个线程执行到这时,暂停了1秒,释放了GIL锁

线程2开始执行,当线程2抢到A锁,需要抢B锁,但是B锁在线程1中,这时线程1开始执行,需要故两个线程互相等待,形成了死锁

3.递归锁

什么是递归锁

  RLock递归锁,可以被第一个抢到锁的人连续的acquire()和release()每次acquire,身上的计数加1,每次release身上的计数减1,只要身上的引用计数不为0,其他任何人都不能抢

 1 from threading import Thread,RLock
 2 
 3 mutex = RLock()
 4 
 5 def task():
 6     mutex.acquire()
 7     print('hello world')
 8     mutex.acquire()
 9     '''
10     使用递归锁,可以避免形成死锁
11     '''
12     print('hi')
13     mutex.release()
14     mutex.release()
15 
16 t = Thread(target=task)
17 t.start()

 

 1 from threading import Thread,Lock,RLock
 2 import time
 3 
 4 # mutexA = Lock()
 5 # mutexB = Lock()
 6 
 7 mutexA = RLock()
 8 '''
 9 RLock递归锁,可以被第一个抢到锁的人连续的acquire()和release()
10 每次acquire,身上的计数加1,每次release身上的计数减1
11 只要身上的引用计数不为0,其他任何人都不能抢
12 '''
13 
14 class MyThread(Thread):
15     def run(self):
16         self.task()
17         self.task1()
18 
19     def task(self):
20         mutexA.acquire()
21         print('%s抢到了A锁'%self.name)
22         mutexA.acquire()
23         print('%s抢到了B锁'%self.name)
24         mutexA.release()
25         print('%s释放了A锁' % self.name)
26         mutexA.release()
27         print('%s释放了B锁' % self.name)
28 
29     def task1(self):
30         mutexA.acquire()
31         print('%s抢到了B锁' % self.name)
32         time.sleep(1)
33         mutexA.acquire()
34         print('%s抢到了A锁' % self.name)
35         mutexA.release()
36         print('%s释放了A锁' % self.name)
37         mutexA.release()
38         print('%s释放了B锁' % self.name)
39 
40 for i in range(4):
41     t = MyThread()
42     t.start()
递归锁的小例子

递归锁可以一定程度上解决死锁

4.信号量

一个互斥锁能控制只有抢到锁的线程在执行锁内的操作,

而信号量可以控制指定数量的线程执行锁内的操作

 1 from threading import Thread,Semaphore
 2 import time
 3 
 4 s = Semaphore(3)  # 允许最大3个线程同时执行
 5 
 6 def task(i):
 7     s.acquire()
 8     print('%s正在执行'%i)
 9     time.sleep(1)
10     s.release()
11 
12 for i in range(10):
13     t = Thread(target=task,args=(i,))
14     t.start()

5.event事件

event通过标志位来判断运行的状态

Event用法:
event=threading.Event() #设置一个事件实例
event.set() #设置标志位
event.clear() #清空标志位
event.wait()  #等待设置标志位
event.is_set  #判断标志位是否被设置
 1 from threading import Thread,Event
 2 import time
 3 import random
 4 
 5 e = Event()  # 生成一个event对象
 6 
 7 def jump():
 8     print('飞机起飞,准备跳伞')
 9     time.sleep(1.1)
10     print('倒计时5秒,机舱打开')
11     for i in range(6):
12         print('
%s'%(5-i),end= '')
13         time.sleep(1)
14     e.set()  # 发送信号
15     print('')
16     print('机舱打开,开始跳伞')
17 
18 
19 def people(i):
20     time.sleep(random.random())
21     print('伞兵%s号,准备完毕'%i)
22     e.wait()  # 等待信号
23     time.sleep(random.random())
24     print('伞兵%s号,开始跳伞'%i)
25 
26 t = Thread(target=jump)
27 t.start()
28 
29 for i in range(1,10):
30     t1 = Thread(target=people,args=(i,))
31     t1.start()

6.队列补充

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

队列:
    import queue  # 导入模块

    queue.Queue()  # 生成队列对象,先进先出
    queue.LifoQueue()  # 生成堆栈对象,后进先出
    queue.PriorityQueue()  # 优先级队列,数字越小,优先级越高
 1 from queue import Queue,LifoQueue,PriorityQueue
 2 
 3 q = Queue()  # 生成队列对象,先进先出
 4 
 5 q.put(1)
 6 q.put(2)
 7 q.put(3)
 8 print(q.get())
 9 print(q.get())
10 print(q.get())
11 
12 q = LifoQueue()  # 堆栈,后进先出
13 q.put(1)
14 q.put(2)
15 q.put(3)
16 print(q.get())
17 print(q.get())
18 print(q.get())
19 
20 q = PriorityQueue()  # 优先级队列,根据传值的大小,小的先出队
21 # 传入元组,第一个值限制了取值时的优先级
22 q.put((1,1))
23 q.put((1,5))
24 q.put((1,3))
25 print(q.get())
26 print(q.get())
27 print(q.get())
队列补充

 32

原文地址:https://www.cnblogs.com/sxchen/p/11354501.html