GIL锁,互斥锁

一、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,导致了部分线程的执行时间被浪费,没有执行。

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)则是基于操作系统提供的进程间通信机制实现的。这意味着线程锁更加轻量级,适用于线程间的同步,而进程锁适用于不同进程之间的同步。

线程类中的锁和进程类中的锁在概念上是相似的,但在实现细节上可能有所不同,适用于不同的执行单位(线程或进程)。

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()

开启进程锁,当一个进程进来后,走了才能有下一个进程进来

互斥锁案例:买票

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}

 

posted @ 2023-07-07 15:39  凡人半睁眼  阅读(69)  评论(0编辑  收藏  举报