Notice: Function _load_textdomain_just_in_time was called incorrectly. Translation loading for the wp-pagenavi domain was triggered too early. This is usually an indicator for some code in the plugin or theme running too early. Translations should be loaded at the init action or later. Please see Debugging in WordPress for more information. (This message was added in version 6.7.0.) in /var/www/blog.zhujinhui.net/wp-includes/functions.php on line 6114

Notice: 函数 _load_textdomain_just_in_time 的调用方法不正确twentyseventeen 域的翻译加载触发过早。这通常表示插件或主题中的某些代码运行过早。翻译应在 init 操作或之后加载。 请查阅调试 WordPress来获取更多信息。 (这个消息是在 6.7.0 版本添加的。) in /var/www/blog.zhujinhui.net/wp-includes/functions.php on line 6114
煅魂-JeffreyChu的修炼屋 – 第 7 页 – 做一个胡思乱想的程序员

Python面试必问5:什么是GIL?

GIL是Python中一个非常重要的概念,几乎所有的python开发面试都会问到这个问题,今天我们来细细盘点下。

概念(重点)

GIL(Global Interpreter Lock,全局解释器锁) 是Python解释器(特别是CPython实现)中的一个机制。它是一种互斥锁,用于保护Python解释器内部的全局状态,确保同时只有一个线程执行Python字节码。换句话说,GIL限制了多线程程序中多个线程的并行(注意不是并发)执行。

GIL产生原因

  • GIL简化了Python解释器的内存管理保护(多)线程安全。Python使用引用计数机制来管理内存,GIL确保了引用计数操作的原子性,避免了多线程环境下引用计数出错的问题。如果没有GIL,多线程同时操作引用计数时,可能会导致内存泄漏或崩溃
  • CPython的历史遗留问题,CPython是Python的最早实现,设计时并没有充分考虑多线程并发的支持。
  • 提高单线程性能
  • 第三方C扩展的兼容性需要进行大改才能确保线程安全。

工作原理

GIL的持有和释放过程可以分为以下几个步骤:

获取GIL
  • 当一个线程要执行Python字节码时,它首先必须获取GIL。
  • 获取GIL的过程是互斥的,即同一时刻只有一个线程能成功获取GIL。
  • 如果GIL已经被其他线程持有,当前线程将进入等待状态,直到GIL被释放。
执行Python代码
  • 一旦线程获取了GIL,它便可以开始执行Python字节码。
  • 线程执行过程中,每经过一定数量的字节码指令(如100个字节码指令),解释器会检查是否需要释放GIL,以便让其他线程有机会执行。
释放GIL
  • 当线程完成了一定数量的字节码指令或进入I/O(如文件读取、网络请求等)操作时,它会释放GIL。
  • 释放GIL后,其他等待的线程可以尝试获取GIL并开始执行。
再度获取GIL

如果一个线程在释放GIL后仍需继续执行,它必须重新尝试获取GIL。这意味着,即使是同一个线程,也无法保证连续长时间持有GIL。
以下是一个简化的示例,描述了GIL的获取和释放:

import threading

def thread_function():
    while True:
        # 尝试获取GIL
        with gil:
            # 执行Python字节码
            execute_bytecode()
            # 每执行一定数量的字节码指令后,检查是否需要释放GIL
            if bytecode_count >= threshold:
                bytecode_count = 0
                # 释放GIL
                release_gil()

# 创建多个线程
threads = [threading.Thread(target=thread_function) for _ in range(10)]
for thread in threads:
    thread.start()

for thread in threads:
    thread.join()

在这个简化的示例中,线程在执行过程中会周期性地释放GIL,让其他线程有机会执行。这种机制虽然限制了多线程的并行性,但也确保了Python解释器的稳定和内存管理的简单性。

影响

CPU密集型任务

GIL限制了多线程程序的并行执行,使得CPU密集型任务无法充分利用多核CPU的计算能力,导致性能提升有限甚至下降。

I/O密集型任务

在I/O密集型任务中,由于I/O操作会释放GIL,其他线程可以在等待I/O操作时执行,从而实现较好的并发性,受GIL的影响较小。因此,多线程在I/O密集型任务中仍然能够提供显著的性能提升。

如何绕过GIL

多进程方案

使用多进程(multiprocessing模块)绕过GIL:

  • 原理:多进程通过创建多个独立的进程来绕过GIL,每个进程都有自己的Python解释器和内存空间,因此它们不会争夺GIL,可以实现真正的并行执行。
  • 实现方法: Python的multiprocessing模块提供了方便的接口来创建和管理多个进程。
  • 优点:可以充分利用多核CPU,真正并行执行,提升性能。
  • 缺点:进程间通信开销较大,内存使用量较多。
原生线程库

使用C扩展或其他语言(如Cython)绕过GIL:

  • 原理:通过使用C语言编写Python扩展模块或使用Cython编写部分代码,可以在扩展模块中释放GIL,让计算密集型任务并行执行。
  • 实现方法:
  • C扩展: 在C代码中使用Python提供的API(如Py_BEGIN_ALLOW_THREADS和Py_END_ALLOW_THREADS)释放和重新获取GIL。
  • Cython: 在Cython代码中,通过声明nogil块来释放GIL。
  • 优点:可以在特定的代码段中实现并行计算,提升性能。
  • 缺点:需要额外的开发工作和学习成本,复杂度增加。
异步编程

Python中的异步编程(asyncio):

  • 原理:异步编程通过事件循环和协程的方式处理并发任务,适用于I/O密集型任务。与多线程不同,异步编程不需要创建多个线程,而是通过单线程处理多个I/O操作,避免了GIL的限制。
  • 实现方法:使用asyncio模块,通过async和await关键字定义和执行异步任务。
  • 优点:高效处理I/O密集型任务,资源占用少,代码相对简洁。
  • 缺点:不适用于CPU密集型任务,需要对异步编程模式有一定的理解。

未来

Python社区一直在探索替代GIL的方案,以提升多线程性能。未来,可能会引入细粒度锁或其他并发模型,但这需要确保向后兼容和性能稳定。PyPy等替代解释器也在积极尝试绕过GIL的限制。

不过目前看来,任重而道远啊!

总结

GIL是Python解释器中的全局锁,限制了多线程并行执行,尤其影响CPU密集型任务,但对I/O密集型任务影响较小。尽管它简化了内存管理,提升了单线程性能,但限制了多核利用。多进程、C扩展和异步编程是绕过GIL的有效方案。

Py面试必问:说说魔术方法与应用

在中高级Python开发面试中,面试官为了考察面试者是否了解面向对象编程,会通过Python魔术方法相关概念来变向提问

引言

定义和基本概念

魔术方法(Magic Methods) 是 Python 中一种特殊的函数,它们用于定义类的特定行为和属性。这些方法通常由 Python 解释器自动调用,而不是显式地在代码中调用。

它们允许你自定义类与内置操作符、函数和语法结构的交互方式,使得自定义对象能够像内置类型一样自然地使用。

命名约定

魔术方法的命名有特定的格式要求,它们都以双下划线__开头和结尾。这种命名约定使得这些方法在类中具有特殊的用途,不会与普通的方法名发生冲突。例如:

学习原因
  • 提升代码可读性和可维护性
  • 实现类的特殊行为和高级功能

常见魔术方法

初始化、删除、调用
  • __init__(self, ...):对象初始化方法(构造函数)
  • __new__(cls, ...):创建对象实例的方法,通常与__init__ 搭配使用
  • __del__(self):对象销毁方法(析构函数)
  • __call__(self, ...):对象被调用时,比如xxx()这样调用形式,就会调用__call__方法的内容
字符串表示
  • __str__(self):定义对象的字符串表示,适用于 str()print()
  • __repr__(self):定义对象的官方字符串表示,适用于 repr(),通常可通过 eval() 函数重建对象
  • __format__(self, format_spec):定义对象的格式化表示,适用于 format() 函数和格式化字符串
  • __bytes__(self):定义对象的字节串表示,适用于 bytes()
数值运算
  • __neg__(self):一元负号(取负),即 -self
  • __pos__(self):一元正号,即 +self
  • __abs__(self):绝对值,即 abs(self)
  • __invert__(self):按位取反,即 ~self
  • __add__(self, other):加法,即 self + other
  • __sub__(self, other):减法,即 self - other
  • __mul__(self, other):乘法,即 self * other
  • __matmul__(self, other):矩阵乘法,即 self @ other
  • __truediv__(self, other):真除法,即 self / other
  • __floordiv__(self, other):取整除法,即 self // other
  • __mod__(self, other):取模,即 self % other
  • __divmod__(self, other):同时计算取整除法和取模,返回 (self // other, self % other)
  • __pow__(self, other, modulo=None):幂运算,即 self ** other
  • __lshift__(self, other):左移位运算,即 self << other
  • __rshift__(self, other):右移位运算,即 self >> other
  • __and__(self, other):按位与,即 self & other
  • __xor__(self, other):按位异或,即 self ^ other
  • __or__(self, other):按位或,即 self | other
  • __radd__(self, other):反向加法,即 other + self
  • __rsub__(self, other):反向减法,即 other - self
  • __rmul__(self, other):反向乘法,即 other * self
  • __rmatmul__(self, other):反向矩阵乘法,即 other @ self
  • __rtruediv__(self, other):反向真除法,即 other / self
  • __rfloordiv__(self, other):反向取整除法,即 other // self
  • __rmod__(self, other):反向取模,即 other % self
  • __rdivmod__(self, other):反向同时计算取整除法和取模,返回 (other // self, other % self)
  • __rpow__(self, other, modulo=None):反向幂运算,即 other ** self
  • __rlshift__(self, other):反向左移位运算,即 other << self
  • __rrshift__(self, other):反向右移位运算,即 other >> self
  • __rand__(self, other):反向按位与,即 other & self
  • __rxor__(self, other):反向按位异或,即 other ^ self
  • __ror__(self, other):反向按位或,即 other | self
  • __iadd__(self, other):增量加法赋值,即 self += other
  • __isub__(self, other):增量减法赋值,即 self -= other
  • __imul__(self, other):增量乘法赋值,即 self *= other
  • __imatmul__(self, other):增量矩阵乘法赋值,即 self @= other
  • __itruediv__(self, other):增量真除法赋值,即 self /= other
  • __ifloordiv__(self, other):增量取整除法赋值,即 self //= other
  • __imod__(self, other):增量取模赋值,即 self %= other
  • __ipow__(self, other, modulo=None):增量幂运算赋值,即 self **= other
  • __ilshift__(self, other):增量左移位赋值,即 self <<= other
  • __irshift__(self, other):增量右移位赋值,即 self >>= other
  • __iand__(self, other):增量按位与赋值,即 self &= other
  • __ixor__(self, other):增量按位异或赋值,即 self ^= other
  • __ior__(self, other):增量按位或赋值,即 self |= other
类型转换
  • __int__(self):定义对象到整数的转换,适用于 int()
  • __float__(self):定义对象到浮点数的转换,适用于 float()
  • __complex__(self):定义对象到复数的转换,适用于 complex()
  • __bool__(self):定义对象的布尔值转换,适用于 bool()
  • __index__(self):定义对象作为索引的转换,适用于切片操作和 bin()hex()oct() 函数
集合操作
  • __len__(self):定义对象的长度,适用于 len()
  • __getitem__(self, key):定义按键访问,适用于 obj[key]
  • __setitem__(self, key, value):定义按键赋值,适用于 obj[key] = value
  • __delitem__(self, key):定义按键删除,适用于 del obj[key]
  • __contains__(self, item):定义成员测试,适用于 item in obj
迭代协议
  • __iter__(self):定义返回迭代器,适用于 iter()
  • __next__(self):定义返回下一个元素,适用于 next()
上下文管理
  • __enter__(self):定义进入上下文管理时的行为,适用于 with 语句
  • __exit__(self, exc_type, exc_value, traceback):定义退出上下文管理时的行为,适用于 with 语句
属性访问
  • __getattr__(self, name):定义访问不存在的属性时的行为
  • __getattribute__(self, name):定义访问任何属性时的行为
  • __setattr__(self, name, value):定义设置属性时的行为
  • __delattr__(self, name):定义删除属性时的行为
描述符协议
  • __get__(self, instance, owner):定义描述符的获取行为
  • __set__(self, instance, value):定义描述符的设置行为
  • __delete__(self, instance):定义描述符的删除行为
比较操作
  • __lt__(self, other):小于,适用于 <
  • __le__(self, other):小于等于,适用于 <=
  • __eq__(self, other):等于,适用于 ==
  • __ne__(self, other):不等于,适用于 !=
  • __gt__(self, other):大于,适用于 >
  • __ge__(self, other):大于等于,适用于 >=

常见应用示例

字符串表示

__str__ 方法用于定义对象的字符串表示,通常用于用户友好的输出,而 __repr__ 方法用于定义对象的官方字符串表示,通常可以用来重建对象。

class Point:
    def __init__(self, x, y):
        self.x = x
        self.y = y

    def __str__(self):
        return f"({self.x}, {self.y})"

    def __repr__(self):
        return f"Point({self.x}, {self.y})"

point = Point(3, 4)
print(str(point))  # 输出:(3, 4)
print(repr(point))  # 输出:Point(3, 4)
排序和比较

通过实现一系列比较运算的魔术方法,可以定义自定义类的排序和比较行为。

class Student:
    def __init__(self, name, score):
        self.name = name
        self.score = score

    def __lt__(self, other):
        return self.score < other.score

    def __eq__(self, other):
        return self.score == other.score

    def __repr__(self):
        return f"Student({self.name}, {self.score})"

students = [
    Student("Alice", 85),
    Student("Bob", 75),
    Student("Charlie", 90)
]
# 根据分数排序
sorted_students = sorted(students)
print(sorted_students)

# 输出:[Student(Bob, 75), Student(Alice, 85), Student(Charlie, 90)]

在这个例子中,通过实现 __lt__ 方法,定义了对象的小于比较。因此可以使用 sorted() 函数对学生列表进行排序。

实现数值类

通过重载算术运算符的魔术方法,可以实现自定义数值类,如复数或矩阵。

class ComplexNumber:
    def __init__(self, real, imag):
        self.real = real
        self.imag = imag

    def __add__(self, other):
        return ComplexNumber(
            self.real + other.real,
            self.imag + other.imag
        )

    def __mul__(self, other):
        return ComplexNumber(
            self.real * other.real - self.imag * other.imag,
            self.real * other.imag + self.imag * other.real
        )

    def __repr__(self):
        return f"{self.real} + {self.imag}i"

# 创建两个复数
c1 = ComplexNumber(2, 3)
c2 = ComplexNumber(4, 5)

# 复数加法
print(c1 + c2)  # 输出:6 + 8i

# 复数乘法
print(c1 * c2)  # 输出:-7 + 22i

在这个例子中,通过重载 __add____mul__ 方法,实现了复数的加法和乘法

集合与序列协议

通过实现集合与序列协议相关的魔术方法,可以定义自定义容器类,使其支持索引和迭代。

比如我们要实现一个既能排序又能去重的SortedSet类:

class SortedSet:
    def __init__(self, items=None):
        self._items = sorted(set(items)) if items else []

    def __repr__(self):
        return f"SortedSet({self._items})"

    def __len__(self):
        return len(self._items)

    def __contains__(self, item):
        return item in self._items

    def __iter__(self):
        return iter(self._items)

    def __getitem__(self, index):
        return self._items[index]

    def add(self, item):
        if item not in self._items:
            self._items.append(item)
            self._items.sort()

    def remove(self, item):
        if item in self._items:
            self._items.remove(item)

# 测试 SortedSet 类
s = SortedSet([3, 1, 2, 3, 4])
print(s)           # 输出:SortedSet([1, 2, 3, 4])
print(len(s))      # 输出:4
print(2 in s)      # 输出:True
print(s[0])        # 输出:1
s.add(5)
print(s)           # 输出:SortedSet([1, 2, 3, 4, 5])
s.remove(2)
print(s)           # 输出:SortedSet([1, 3, 4, 5])
属性访问控制

通过实现属性访问相关的魔术方法,可以实现动态属性和代理模式。

class DynamicAttribute:
    def __getattr__(self, name):
        if name == "age":
            return 25
        elif name == "name":
            return "Alice"
        else:
            raise AttributeError(
                f"'DynamicAttribute' object has no attribute '{name}'"
            )

obj = DynamicAttribute()
print(obj.age)  # 输出:25
print(obj.name)  # 输出:Alice

在这个例子中,通过实现 __getattr__ 方法,动态地为对象添加属性。如果属性不存在,则会调用这个方法来获取属性值,这在Python一些的ORM框架中很常见。

构造查询语句

python中的运算符|&其实可以对标SQL中的orand,所以我们可以通过魔术方法来实现一些类似SQL语句拼接

# 注:代码块暂不考虑太多异常情况,比如not操作
class Q:
    def __init__(self, *expressions, operator="and", **kwargs):
        self.operator = operator
        self._expressions = expressions
        self._kwargs = kwargs

    def __or__(self, other):
        return Q(self, other, operator="or")

    def __str__(self):
        return self.translate()

    def translate(self):
        kv_sql = " and ".join([f"{k}={v}" for k, v in self._kwargs.items()])
        expr_sql = f" {self.operator} ".join([expr.translate() for expr in self._expressions])
        return expr_sql + (f" {self.operator} " + kv_sql if expr_sql and kv_sql else kv_sql)


# 示例用法
q1 = Q(name='Alice', height=168)
q2 = Q(age=25, gender=0)
q3 = Q(q1, age=25)

# 实现 OR 运算符
print(q1 | q2)
# 输出:name=Alice and height=168 or age=25 and gender=0
print(q3)
# 输出:name=Alice and height=168 and age=25

上面例子中kwargs参数代表的是参与and操作的拼接键值对参数,而|则表示两个对象要进行or操作的拼接,这样就可以实现类似ORM框架里的还原SQL字符串 拼接操作了

上下文管理

上下文管理是 Python 中用于管理资源的一种机制,它可以确保在进入和离开代码块时资源得到正确的管理和释放。在使用 with 语句时,Python 会调用对象的 __enter__ 方法来获取上下文管理器,并在离开代码块时调用 __exit__ 方法来释放资源。

class MyResource:
    def __enter__(self):
        print("Entering the resource")
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print("Exiting the resource")

    def operation(self):
        print("Performing operation")

# 使用上下文管理器
with MyResource() as resource:
    resource.operation()

# Outputs
# Entering the resource
# Performing operation
# Exiting the resource

在这个示例中,MyResource 类实现了上下文管理器。当进入 with 代码块时,Python 会调用 __enter__ 方法,然后执行其中的代码;当离开 with 代码块时,Python 会调用 __exit__ 方法,以确保资源被正确释放。

可调用对象

__call__ 方法使得一个对象可以像函数一样被调用,这对于实现可调用对象非常有用。当调用对象时,Python 解释器会自动调用对象的 __call__ 方法。

class MyCallable:
    def __call__(self, x):
        return x * 2

# 创建可调用对象
callable_obj = MyCallable()

# 调用可调用对象
result = callable_obj(5)
print(result)  # 输出:10

在这个示例中,MyCallable 类实现了 __call__ 方法,使得它的实例可以像函数一样被调用。当调用 callable_obj(5) 时,Python 实际上调用了 callable_obj.__call__(5)

__new____init__

__new__ 方法和 __init__ 方法都是 Python 中用于创建类实例的特殊方法,但它们的作用略有不同。

__new__ 方法在实例创建之前调用,用于创建实例对象,并返回一个新创建的实例。
__init__ 方法在实例创建之后调用,用于初始化实例对象的属性。

class MyClass:
    def __new__(cls, *args, **kwargs):
        print("Creating instance")
        instance = super().__new__(cls)
        return instance

    def __init__(self, x):
        print("Initializing instance")
        self.x = x

# 创建实例
obj = MyClass(5)

# Outputs
# Creating instance
# Initializing instance

在这个示例中,__new__ 方法在实例创建之前被调用,用于创建实例对象,而 __init__ 方法在实例创建之后被调用,用于初始化实例对象的属性。

总结

Python 魔术方法是一种特殊的命名约定,用双下划线包围,用于实现对象的特殊行为和操作。它们包括但不限于初始化、字符串表示、比较、算术运算、上下文管理等方面。通过合理使用魔术方法,可以提高代码的可读性、可维护性,并实现更灵活的编程逻辑,是面向对象编程里一种非常重要的实现方法。