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操作时不会耗费时间

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)
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('\r%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())
