一、GIL锁
1、全局解释器锁(Global Interpreter Lock,简称 GIL)
GIL 是一种用于保护 Python 解释器在多线程环境下的数据完整性的机制。GIL 只存在是 CPython 解释器中,即官方的 Python 解释器实现
GIL 是一个互斥锁,你可以使用多线程来并发处理任务,但在同一时刻只能有一个线程在执行 Python 字节码。
2、存在的背景
GIL 的存在主要是因为 CPython 的内存管理机制(垃圾回收机制,python有一个垃回收线程)并不是线程安全的。
Python 中的对象引用计数机制(reference counting)是一种快速且高效的垃圾回收机制,但它对于多线程环境来说需要保证引用计数的一致性,这就需要 GIL 来确保同一时间只有一个线程能够访问和修改对象的引用计数。
由于 GIL 的存在,导致了 Python 在 CPU 密集型任务上的多线程性能并不理想。因为在这种情况下,即使使用了多个线程,由于 GIL 的限制,多个线程也不能真正并行执行,只能交替执行,因此无法充分利用多核 CPU 的优势。
然而,GIL 在 I/O 密集型任务上的影响较小。当线程执行 I/O 操作(如文件读写、网络请求)时,线程会主动释放 GIL,让其他线程有机会执行,从而提高了并发性能。
需要注意的是,GIL 只存在于 CPython 解释器中。其他一些 Python 解释器实现(如 Jython、IronPython)没有 GIL,它们可以更好地利用多线程并发执行。
GIL 是 Python 解释器中的一种机制,用于保护解释器在多线程环境下的数据一致性。
虽然 GIL 限制了多线程的并行性能,但在 I/O 密集型任务中仍然可以提供一定的并发优势。
如果你需要在 Python 中处理 CPU 密集型任务,并发执行的需求较大,可以考虑使用多进程、异步编程或者使用其他 Python 解释器实现来绕过 GIL 的限制。
案例
创建了5个线程,每个线程都执行一个自增函数,该函数将一个全局计数器 counter
的值自增100万次。
由于 GIL 的限制,这段代码在多线程环境下并不能实现预期的并行效果。最终输出的计数器值通常会小于500万,而不是我们期望的500万(即每个线程自增100万次,共5个线程)。
这是因为 GIL 会确保在任何同一时间内只有一个线程在执行 Python 代码,导致多个线程无法同时访问和修改共享的计数器变量。没有抢到GIL锁的线程就会被释放掉。线程会交替地获取和释放 GIL,导致了部分线程的执行时间被浪费,没有执行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
import threading import time # 全局变量 counter = 0 # 自增函数 def increment(): global counter for i in range ( 1000000 ): counter + = 1 # 创建并启动多个线程 threads = [] for i in range ( 5 ): thread = threading.Thread(target = increment) thread.start() threads.append(thread) # 等待所有线程执行完成 for thread in threads: thread.join() # 输出最终的计数器值 print ( "Final counter value:" , counter) # Final counter value: 2301450 |
二、互斥锁(mutex)
1、互斥锁有进程锁和线程锁
互斥锁可以将并发变成串行,牺牲效率来保证数据的安全性。
线程锁(
Lock
)是基于线程的互斥量实现的,而进程锁(Lock
)则是基于操作系统提供的进程间通信机制实现的。这意味着线程锁更加轻量级,适用于线程间的同步,而进程锁适用于不同进程之间的同步。线程类中的锁和进程类中的锁在概念上是相似的,但在实现细节上可能有所不同,适用于不同的执行单位(线程或进程)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
from multiprocessing import Process from multiprocessing import Lock def task(i, lock): lock.acquire() print ( '第%s个进程来了' % i) print ( '第%s个进程走了' % i) lock.release() if __name__ = = '__main__' : lock = Lock() for i in range ( 5 ): p = Process(target = task, args = (i, lock)) # 把锁当作位置参数传进去 p.start() |
开启进程锁,当一个进程进来后,走了才能有下一个进程进来
互斥锁案例:买票
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
import json import time import random from multiprocessing import Lock, Process # 查票 def check_ticket(i): with open ( 'a.txt' , 'r' ) as f: dic = json.load(f) print ( '用户%s查询余票:%s' % (i, dic.get( 'ticket_num' ))) # 买票 def buy(i): with open ( 'a.txt' , 'r' ) as f: dic = json.load(f) # 模拟网络延迟 time.sleep(random.randint( 1 , 3 )) # 判断当前是否有票 if dic.get( 'ticket_num' ) > 0 : # 修改数据库,买票 dic[ 'ticket_num' ] - = 1 # 写入数据库 with open ( 'a.txt' , 'w' ) as f1: json.dump(dic, f1) print ( '用户%s买票成功' % i) else : print ( '用户%s买票失败' % i) def run(i, lock): check_ticket(i) # 买票环节加锁处理 lock.acquire() buy(i) lock.release() if __name__ = = '__main__' : # 在主进程生成锁 lock = Lock() # 多进程 for i in range ( 1 , 11 ): p = Process(target = run, args = (i, lock)) p.start() |
注意:a.txt 中的数据是以json格式存在的 {"ticket_num": 1}