当前位置:   article > 正文

【Python】线程—GIL—asyncio_python 线程 gil

python 线程 gil

一、Python 线程

线程是一种轻量级的并行执行方式,它可以让我们在一个程序中同时执行多个任务。线程编程是多任务处理的一种特殊形式,它允许我们创建多个线程来执行不同的任务。

多线程通常是为了利用计算机的多核CPU(并行处理),来提升程序的运行速度。然而在多核CPU出现的几十年之前就出现多线程的概念了,其目的是不显式的切换任务的情况下,让CPU可以并发的处理若干个任务,提高资源利用率。

因为GIL的存在,有人说“python的多线程没有意义”。的确,由于GIL的存在,python的多线程无法像其它语言一样通过利用多核CPU提高程序运行速度,但其在处理IO瓶颈任务和处理需要低延迟的小任务时,仍然具有优势。同时,多线程也可解决协程调度的问题。

我第一次使用python线程是GUI程序,当主窗口打开一个子窗口的时候,子窗口如果有正在处理的任务,主窗口就会卡住,所以使用了线程。
在这里插入图片描述

二、threading 模块

Python的threading模块提供了多个类来支持多线程编程

  1. Thread类:这是threading模块中最核心的类。它代表了一个线程,可以独立执行任务。创建Thread对象时,可以传递一个callable对象作为目标函数。Thread类还有一个重要的方法start()用于启动线程,以及join()方法用于等待线程完成。
  2. Lock类:用于实现线程间的互斥锁机制,确保同一时间只有一个线程能够访问特定的资源或代码段。它有两个主要的方法:acquire()release(),分别用于获取和释放锁。
  3. RLock类:与Lock类似,但RLock允许同一个线程多次获得锁,这在递归调用时非常有用。
  4. Semaphore类:是一个更高级的锁机制,允许一定数量的线程同时访问资源。Semaphore维护了一个计数器,通过acquire()release()方法来增加或减少计数值。
  5. Event类:用于实现线程间的同步,一个线程可以通过Event对象向另一个线程发送信号。Event对象有一个内部标志,可以通过set()clear()方法来设置和清除这个标志。
  6. Condition类:类似于Event,但提供了更复杂的同步机制。它允许线程等待某个条件成立,然后才继续执行。通常与Lock或RLock一起使用。
  7. Barrier类:用于实现线程间的同步屏障,当所有线程都达到某个点时,它们才会被允许继续执行。
  8. Timer类:用于在指定的延迟后调用一个函数。它不是直接用于线程同步,但可以用于安排未来的任务。
  9. ThreadLocal类:提供了线程局部数据的功能,允许每个线程拥有自己独立的对象实例。

部分方法:

方法描述
Threadstart()开始线程执行。
run()线程要执行的方法。默认情况下是调用target参数指定的函数,可以被子类重写。
join(timeout=None)等待线程终止。如果设置了timeout,则最多等待timeout秒。
is_alive()判断线程是否在运行。
getName()获取线程名称。
setName(name)设置线程名称。
Lockacquire(blocking=True, timeout=-1)获取锁。blocking=True时,阻塞直到获得锁或超时;blocking=False时,非阻塞获取锁。timeout指定非阻塞等待时间。
release()释放锁。
locked()检查锁的状态。
RLockacquire(blocking=True, timeout=-1)获取重入锁。行为类似于Lock,但支持同一线程多次获取锁。
release()释放重入锁。
locked()检查锁的状态。
Conditionacquire(blocking=True, timeout=-1)获取锁,用于线程间同步。行为类似于Lock,但用于线程间协调。
release()释放锁,用于线程间同步。
wait(timeout=None)等待直到被通知或超时。必须在已获得锁的情况下调用。
notify(n=1)通知等待的线程,至少通知n个线程。
notify_all()通知所有等待的线程。
Semaphoreacquire(blocking=True, timeout=None)获取信号量。类似于Lock,但允许多个线程同时访问临界区,但有一定限制。
release()释放信号量。
Eventset()设置事件标志为True,通知等待该事件的所有线程。
clear()设置事件标志为False。
is_set()检查事件标志是否为True。
Timerstart()开始计时器线程。在指定时间后调用指定函数。
cancel()取消计时器。如果计时器仍在等待运行,则取消。

三、例程

3.1 基本用法

import threading


def my_function():
    print("Thread {} is running...".format(threading.Thread.getName(my_thread)))


# 创建线程
my_thread = threading.Thread(target=my_function, name="myThread")

# 启动线程
my_thread.start()

# 等待线程执行完成
my_thread.join()

print("Main thread ends.")

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18

3.2 同步

Python中线程同步的方法有以下几种:

  1. 锁(Lock):这是最基本的同步机制,用于确保同一时间只有一个线程能够访问特定的资源或代码段。通过acquire()方法加锁和release()方法解锁来实现线程间的互斥。
  2. 递归锁(RLock):与普通锁类似,但允许同一个线程多次获得锁,适用于递归调用的情况。
  3. 信号量(Semaphore):用于控制同时访问特定资源的线程数量。当一个线程完成对资源的访问后,会释放信号量,允许其他线程进入。
  4. 事件(Event):用于通知所有等待的线程某个事件已经发生。线程可以通过wait()方法等待事件发生,通过set()方法来通知事件已发生。
  5. 条件变量(Condition):允许线程等待某些条件成立,然后才继续执行。通常与锁一起使用,以防止多个线程同时改变条件。
  6. 队列(Queue):提供了一种适合多线程编程的数据结构,可以在不同线程之间安全地传递消息。
  7. 全局解释器锁(GIL):虽然不是直接由程序员控制的同步机制,但它是Python中的一个内置机制,用于确保在任何给定时刻,只有一个线程能够执行Python字节码。

它们的特点和适用场景:

工具特点适用场景
Lock最基本的互斥锁,一次只允许一个线程访问共享资源
不可重入,即同一线程再次获取会导致死锁
简单的线程同步需求
需要确保一段代码同一时间只能被一个线程执行
RLock可重入锁,同一线程可以多次获取锁并释放
允许同一线程多次调用 acquire()
复杂的递归线程同步需求
某些情况下需要允许同一线程多次获取和释放锁
Semaphore允许一定数量的线程同时访问共享资源
控制并发数量
有限资源的并发控制
控制同时运行的线程数量,比如限流
Event可以通过 set() 和 clear() 设置和清除事件状态
线程可以等待事件的发生
线程间通信和同步
一个线程等待某个事件的发生,另一个线程触发事件
Condition提供了更高级的线程同步机制,结合了锁和事件复杂的线程协调和通信需求
- 允许线程等待某个条件,其他线程在满足条件时通知等待的线程继续执行

3.21 Lock(锁)

  • threading.Lock 类提供了最基本的线程同步机制,它可以确保一次只有一个线程可以访问共享资源。
  • acquire() 方法用于获取锁,release() 方法用于释放锁。
  • 示例:
import threading
import time

# 创建一个锁对象
lock = threading.Lock()


def worker():
    # 获取锁
    lock.acquire()
    try:
        # 执行需要同步的操作
        print("Thread {} is working...".format(threading.Thread.getName(t)))
        # 模拟耗时操作
        time.sleep(1)
    finally:
        # 释放锁
        lock.release()


# 创建多个线程并启动它们
threads = []
for i in range(5):
    t = threading.Thread(target=worker)
    threads.append(t)
    t.start()

# 等待所有线程完成
for t in threads:
    t.join()

print("All threads finished.")

  • 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

3.22 RLock(递归锁)

  • threading.RLock 是一个可重入锁,允许同一线程多次获得锁。
  • acquire()release() 方法的使用方式与 Lock 类似,但允许同一线程多次调用 acquire()
  • 示例:
import threading
import time


class Counter:
    def __init__(self):
        self.value = 0
        self.lock = threading.RLock()

    def increment(self):
        with self.lock:
            self.value += 1
            print(f"Incremented to {self.value} by {threading.currentThread().getName()}")
            time.sleep(0.1)  # 模拟一些计算或I/O操作

    def decrement(self):
        with self.lock:
            self.value -= 1
            print(f"Decremented to {self.value} by {threading.currentThread().getName()}")
            time.sleep(0.1)  # 模拟一些计算或I/O操作


def worker(counter):
    for _ in range(3):
        counter.increment()
        counter.decrement()


# 创建 Counter 实例
counter = Counter()

# 创建多个线程
threads = []
for i in range(3):
    thread = threading.Thread(target=worker, args=(counter,))
    threads.append(thread)
    thread.start()

# 等待线程执行完成
for thread in threads:
    thread.join()

print("Final counter value:", counter.value)

  • 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
  • 44

3.23 Condition(条件变量)

  • threading.Condition 是一个高级的线程同步工具,同时提供了锁和条件等待/通知机制。
  • acquire()release() 方法用于加锁和解锁,wait() 方法用于等待条件的通知,notify()notify_all() 方法用于发送通知。
  • 示例:
import threading

shared_resource = []
condition = threading.Condition()

def consumer():
    with condition:
        print("Consumer waiting...")
        condition.wait()
        print("Consumer consumed the resource:", shared_resource.pop(0))

def producer():
    with condition:
        print("Producer producing resource...")
        shared_resource.append("New Resource")
        condition.notify()
        print("Producer notified the consumer.")

# 创建线程
consumer_thread = threading.Thread(target=consumer)
producer_thread = threading.Thread(target=producer)

# 启动线程
consumer_thread.start()
producer_thread.start()

# 等待线程执行完成
consumer_thread.join()
producer_thread.join()

print("Main thread ends.")
  • 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

3.24 Semaphore(信号量)

  • threading.Semaphore 是一种控制并发访问的计数器,它允许多个线程同时访问共享资源,但可以限制同时访问的线程数量。
  • acquire()release() 方法用于获取和释放信号量。
  • 示例:
import threading

semaphore = threading.Semaphore(value=2)  # 允许同时两个线程访问

def access_resource():
    with semaphore:
        print(threading.currentThread().getName(), "is accessing the resource.")
        # 假设这里是对共享资源的访问

# 创建多个线程
threads = []
for i in range(5):
    thread = threading.Thread(target=access_resource)
    threads.append(thread)
    thread.start()

# 等待线程执行完成
for thread in threads:
    thread.join()

print("Main thread ends.")
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

这些是 Python 中常用的线程同步方法。选择合适的方法取决于你的应用场景,例如是否需要多次获取锁、是否需要等待条件、是否需要限制并发数量等。这些同步工具能够有效地管理多线程程序中的竞态条件,确保线程安全地访问共享资源。

四、GIL

4.1 简述

Python的全局解释器锁Global Interpreter Lock,简称GIL)是CPython解释器中的一种线程同步机制。具体如下:

  1. 原理与作用:GIL是一种互斥锁,它确保在任何时刻只有一个线程执行Python字节码。这意味着即使在多核CPU上,使用多线程的Python程序也无法实现真正的并行执行
  2. 优缺点:GIL的存在简化了内存管理和解释器的实现,因为不需要担心多个线程同时修改内存中的数据结构。然而,这也限制了多线程在计算密集型任务中的应用,因为GIL会阻止多个线程同时利用多核处理器的优势。
  3. 性能瓶颈:对于I/O密集型任务,GIL的影响相对较小,因为线程大部分时间都在等待I/O操作,而不是执行计算。但是,对于计算密集型任务,GIL可能导致性能瓶颈,因为它限制了多线程的并行能力。
  4. 解决方案:为了克服GIL的限制,可以使用多进程代替多线程(多进程未必使程序更快),因为每个进程都有自己的Python解释器和GIL,从而可以在多核CPU上并行运行。此外,还可以使用Jython或IronPython这样的替代Python解释器,它们没有GIL的限制。
  5. 未来展望:Python社区正在努力解决GIL的问题。例如,Python 3.12引入了GIL可选项,允许在编译时关闭GIL,以提高CPU密集型场景的性能。

cpython的PR中可以看到前几天的一条PR,即添加GIL的开关。
在这里插入图片描述
当然不是release版,能用到可能还需要很久。

有时候,限制程序性能的可能不是GIL,而是程序的生产者。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/2023面试高手/article/detail/687160
推荐阅读
相关标签