一、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}
标签:解释器,多线程,Python,lock,互斥,线程,GIL From: https://www.cnblogs.com/Jessica-Jmm/p/18037313