Python GIL 解析:为什么多线程不总是更快?

本文介绍了Python中实现并发编程的不同策略,包括多线程、多进程、协程、多进程加多线程、并行计算和异步编程,以及并发控制和线程/进程池的使用,以优化任务执行和资源管理。
部署运行你感兴趣的模型镜像

在Python中,有多种方法来实现并发编程,以便同时执行多个任务。以下是一些主要的并发编程方式:

  1. 多线程(Multithreading):使用threading模块创建多个线程,每个线程可以并发执行任务。多线程适合处理I/O密集型任务,但需要小心处理共享资源和锁定。

  2. 多进程(Multiprocessing):使用multiprocessing模块创建多个进程,每个进程可以并发执行任务。多进程适合处理CPU密集型任务,而且每个进程拥有独立的内存空间,不需要担心全局解释器锁(GIL)。

  3. 协程(Coroutines):使用asyncio库来创建协程,允许在单线程中并发执行异步任务。协程适合处理I/O密集型任务,可以有效地减少线程或进程的开销。

  4. 多进程加多线程(Multiprocessing and Multithreading):可以同时使用多进程和多线程,以充分利用多核处理器和多线程的优势。这种方式在某些情况下可以提高性能。

  5. 并行计算(Parallel Computing):使用库如concurrent.futuresmultiprocessing来并行执行函数或任务。这种方式适用于将任务分配给多个处理器核心,以加速计算。

  6. 异步编程(Asynchronous Programming):使用async/await关键字和异步库(如asyncioaiohttp等)来执行非阻塞、事件驱动的编程。这对于构建高性能网络服务和I/O密集型应用程序非常有用。

  7. 并发控制(Concurrency Control):使用同步原语(如锁、信号量、条件变量等)来控制多个任务的并发访问共享资源,以避免竞争条件。

  8. 线程池(Thread Pool)和进程池(Process Pool):使用concurrent.futures库来管理线程池和进程池,以便执行并发任务,避免频繁创建和销毁线程或进程的开销。

    不同的并发方式适用于不同的应用场景和需求。选择合适的并发方法取决于你的应用程序的性质和要解决的问题。要注意的是,并发编程可能涉及到共享资源的同步和竞争条件处理,需要小心处理以避免潜在的问题。

1.1 多线程(Multithreading)

      Python的threading模块来创建多线程,以实现从1到100的计数任务。下面是一个简单的示例,演示如何创建多个线程来并行执行计数任务:

import threading

# 定义一个共享的计数变量
count = 0
# 定义锁,用于同步多个线程对计数变量的访问
count_lock = threading.Lock()

# 定义一个计数的函数
def increment():
    global count
    for i in range(1, 101):
        with count_lock:
            count += 1

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

# 启动所有线程
for thread in threads:
    thread.start()

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

# 打印最终计数结果
print("Final count:", count)

2.1 多进程(Multiprocessing)使用multiprocessing模块

      要在Python中使用多进程来实现从1到100的计数任务,你可以使用multiprocessing模块。下面是一个示例代码,展示如何创建多个进程并并行执行计数任务:

import multiprocessing

# 定义一个计数的函数
def increment(start, end, result):
    total = 0
    for i in range(start, end + 1):
        total += i
    result.put(total)

if __name__ == "__main__":
    # 创建多个进程
    num_processes = 4  # 你可以根据需要更改进程的数量
    processes = []

    # 创建进程间通信的队列,用于接收各个进程的计数结果
    result_queue = multiprocessing.Queue()

    # 分割任务范围并创建进程
    step = 100 // num_processes
    for i in range(num_processes):
        start = i * step + 1
        end = (i + 1) * step if i < num_processes - 1 else 100
        process = multiprocessing.Process(target=increment, args=(start, end, result_queue))
        processes.append(process)

    # 启动所有进程
    for process in processes:
        process.start()

    # 等待所有进程完成
    for process in processes:
        process.join()

    # 从队列中获取各个进程的计数结果并累加
    total_count = 0
    while not result_queue.empty():
        total_count += result_queue.get()

    # 打印最终计数结果
    print("Final count:", total_count)

3.1 协程(Coroutines)使用asyncio库来创建协程

    使用协程(coroutine)来实现从1到100的计数任务,你可以使用Python的asyncio库。下面是一个示例代码,演示如何创建一个协程来执行这个任务:

import asyncio

async def count_numbers():
    total = 0
    for i in range(1, 101):
        total += i
        await asyncio.sleep(0)  # 使用await以允许事件循环切换任务
    return total

async def main():
    total_count = await count_numbers()
    print("Final count:", total_count)

if __name__ == "__main__":
    asyncio.run(main())

   我们定义了一个count_numbers协程来计数从1到100的总和,使用await asyncio.sleep(0)来允许事件循环在每次循环中切换任务,以确保协程不会阻塞事件循环。然后,我们定义了一个main协程来启动count_numbers协程,并等待它完成。最后,我们使用asyncio.run来运行主协程。

    这个示例演示了如何使用协程来执行计数任务,协程可以充分利用事件循环并发执行任务,而不会阻塞主线程。请注意,Python的协程通常用于异步编程,用于处理I/O密集型任务,但也可以用于一般的计算任务。

4.1 多进程加多线程(Multiprocessing and Multithreading)

    Python中,你可以使用多进程和多线程的组合来实现从1到100的并发计算任务。下面是一个示例代码,演示了如何使用多进程和多线程的组合来完成这个任务:

import multiprocessing
import threading

def worker(start, end, result):
    total = 0
    for i in range(start, end + 1):
        total += i
    result.append(total)

def main():
    num_processes = 4  # 指定进程数
    num_threads_per_process = 2  # 指定每个进程中的线程数
    result = multiprocessing.Manager().list()  # 用于保存结果的共享列表

    # 创建多个进程
    processes = []
    for _ in range(num_processes):
        process = multiprocessing.Process(target=process_worker, args=(num_threads_per_process, result))
        processes.append(process)
        process.start()

    # 等待所有进程完成
    for process in processes:
        process.join()

    # 计算总和
    total_sum = sum(result)
    print("Total sum from 1 to 100:", total_sum)

def process_worker(num_threads, result):
    # 每个进程中创建多个线程
    threads = []
    for _ in range(num_threads):
        thread = threading.Thread(target=worker, args=(1, 100, result))
        threads.append(thread)
        thread.start()

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

if __name__ == "__main__":
    main()

   在这个示例中,worker函数负责计算指定范围内的总和,process_worker函数负责在每个进程中创建多个线程来执行worker函数。main函数中创建了指定数量的进程,每个进程中包含了指定数量的线程。这样,每个进程内的线程可以并发地执行计算任务,多个进程之间也可以并发地执行。

   请注意,在实际应用中,你需要根据你的计算机硬件和任务性质来调整进程数和线程数,以获得最佳性能。同时,对于共享资源的访问,需要使用适当的同步机制(如锁)来避免竞争条件。

5.1 并行计算(Parallel Computing)使用库如concurrent.futures和multiprocessing来并行执行函数或任务

    你可以使用concurrent.futuresmultiprocessing库来进行并行计算,从1到100并行执行计算任务。以下是一个示例代码,演示如何使用concurrent.futuresmultiprocessing来实现这个任务:

import concurrent.futures

def worker(start, end):
    total = sum(range(start, end + 1))
    return total

def main():
    num_workers = 4  # 指定工作进程数
    chunk_size = 25  # 每个工作进程处理的范围大小
    total_sum = 0

    with concurrent.futures.ProcessPoolExecutor(max_workers=num_workers) as executor:
        futures = []
        for i in range(1, 101, chunk_size):
            start = i
            end = min(i + chunk_size - 1, 100)
            future = executor.submit(worker, start, end)
            futures.append(future)

        for future in concurrent.futures.as_completed(futures):
            total_sum += future.result()

    print("Total sum from 1 to 100:", total_sum)

if __name__ == "__main__":
    main()

   在这个示例中,worker函数负责计算指定范围内的总和。main函数中创建了一个ProcessPoolExecutor,并指定了工作进程的数量(num_workers)。然后,循环遍历范围从1到100,将任务分成多个小块,每个小块交由一个工作进程处理。executor.submit方法将任务提交给工作进程,并返回一个Future对象,表示任务的未来结果。通过as_completed方法等待所有Future对象完成,然后累加各块的计算结果以获得总和。

    这种方法使用concurrent.futures库,可以方便地并行执行函数或任务,而无需手动管理多进程或多线程。你可以根据需要调整num_workerschunk_size,以优化性能和资源利用。这个示例中使用了多进程,但你也可以使用ThreadPoolExecutor来使用多线程进行并行计算。

6.1 异步编程(Asynchronous Programming)使用async/await关键字和异步库(如asyncio、aiohttp等)来执行非阻塞、事件驱动的编程

    在 Python 中,你可以使用异步编程来执行非阻塞、事件驱动的任务。下面是一个示例代码,演示如何使用 async/await 关键字和 asyncio 库来执行从 1 到 100 的异步计算任务:

import asyncio

async def worker(start, end):
    total = sum(range(start, end + 1))
    return total

async def main():
    num_tasks = 4  # 指定并行任务数
    chunk_size = 25  # 每个任务处理的范围大小
    total_sum = 0

    tasks = []
    for i in range(1, 101, chunk_size):
        start = i
        end = min(i + chunk_size - 1, 100)
        task = asyncio.create_task(worker(start, end))
        tasks.append(task)

    for task in tasks:
        total_sum += await task

    print("Total sum from 1 to 100:", total_sum)

if __name__ == "__main__":
    asyncio.run(main())

   在这个示例中,worker 函数使用 async 关键字标记为异步函数,负责计算指定范围内的总和。main 函数也是异步函数,它创建了多个异步任务(使用 asyncio.create_task),每个任务处理一个小范围的计算任务。然后,通过 await 关键字等待所有任务完成,并累加各任务的计算结果以获得总和。

   这种方法利用了 Python 中的异步编程特性,允许多个任务并行执行而无需显式创建多进程或多线程。你可以根据需要调整 num_tasks 和 chunk_size,以控制并行性和资源利用。

如果你需要进行异步网络请求,可以使用 aiohttp 等库来执行异步的 HTTP 请求。异步编程在处理 I/O 密集型任务时非常有用,可以提高程序的效率。

7.1 并发控制(Concurrency Control)

    在 Python 中,你可以使用并发控制来确保多个任务按照特定顺序执行或限制同时执行的任务数量。以下是两种常见的并发控制方法:线程和进程。你可以使用 threading 模块进行线程控制,使用 multiprocessing 模块进行进程控制。

    线程控制(使用 threading 模块):

import threading

def worker(start, end):
    total = sum(range(start, end + 1))
    print(f"Sum from {start} to {end}: {total}")

def main():
    num_threads = 4  # 指定线程数量
    chunk_size = 25  # 每个线程处理的范围大小

    threads = []
    for i in range(1, 101, chunk_size):
        start = i
        end = min(i + chunk_size - 1, 100)
        thread = threading.Thread(target=worker, args=(start, end))
        threads.append(thread)
        thread.start()

    for thread in threads:
        thread.join()

if __name__ == "__main__":
    main()

   在上述示例中,worker 函数计算指定范围内的总和。main 函数创建了多个线程,每个线程处理一个小范围的计算任务。threading.Thread 类用于创建线程对象,然后通过 start() 方法启动线程,最后使用 join() 方法等待所有线程执行完毕。

进程控制(使用 multiprocessing 模块)

import multiprocessing

def worker(start, end):
    total = sum(range(start, end + 1))
    print(f"Sum from {start} to {end}: {total}")

def main():
    num_processes = 4  # 指定进程数量
    chunk_size = 25  # 每个进程处理的范围大小

    processes = []
    for i in range(1, 101, chunk_size):
        start = i
        end = min(i + chunk_size - 1, 100)
        process = multiprocessing.Process(target=worker, args=(start, end))
        processes.append(process)
        process.start()

    for process in processes:
        process.join()

if __name__ == "__main__":
    main()

    在上述示例中,worker 函数的功能与之前相同。main 函数创建了多个进程,每个进程处理一个小范围的计算任务。multiprocessing.Process 类用于创建进程对象,然后通过 start() 方法启动进程,最后使用 join() 方法等待所有进程执行完毕。

    无论是使用线程还是进程,这两种方法都允许你控制并发执行的任务数量,可以根据需要调整 num_threads 或 num_processes 变量。

8.1 线程池(Thread Pool)和进程池(Process Pool)

    可以使用线程池和进程池来更有效地管理线程和进程的资源,以执行从1到100的任务。这可以帮助你限制并发线程或进程的数量,提高执行效率。在 Python 中,你可以使用 concurrent.futures 模块来创建线程池和进程池。以下是示例代码:

线程池(ThreadPoolExecutor):

import concurrent.futures

def worker(start, end):
    total = sum(range(start, end + 1))
    return total

def main():
    num_threads = 4  # 指定线程池中的线程数量
    chunk_size = 25  # 每个任务处理的范围大小

    with concurrent.futures.ThreadPoolExecutor(max_workers=num_threads) as executor:
        futures = []
        for i in range(1, 101, chunk_size):
            start = i
            end = min(i + chunk_size - 1, 100)
            future = executor.submit(worker, start, end)
            futures.append(future)

        total_sum = 0
        for future in concurrent.futures.as_completed(futures):
            total_sum += future.result()

    print("Total sum from 1 to 100:", total_sum)

if __name__ == "__main__":
    main()

在上述示例中,我们使用 concurrent.futures.ThreadPoolExecutor 创建线程池,并在线程池中提交任务。executor.submit 用于将任务提交到线程池,并返回一个 Future 对象,通过 future.result() 获取任务的结果。concurrent.futures.as_completed 用于等待所有任务完成。

进程池(ProcessPoolExecutor):

import concurrent.futures

def worker(start, end):
    total = sum(range(start, end + 1))
    return total

def main():
    num_processes = 4  # 指定进程池中的进程数量
    chunk_size = 25  # 每个任务处理的范围大小

    with concurrent.futures.ProcessPoolExecutor(max_workers=num_processes) as executor:
        futures = []
        for i in range(1, 101, chunk_size):
            start = i
            end = min(i + chunk_size - 1, 100)
            future = executor.submit(worker, start, end)
            futures.append(future)

        total_sum = 0
        for future in concurrent.futures.as_completed(futures):
            total_sum += future.result()

    print("Total sum from 1 to 100:", total_sum)

if __name__ == "__main__":
    main()

   在上述示例中,我们使用 concurrent.futures.ProcessPoolExecutor 创建进程池,其余部分与线程池示例类似。

   无论是线程池还是进程池,它们都可以帮助你管理并发执行的任务,通过调整 max_workers 变量来控制并发性。

您可能感兴趣的与本文相关的镜像

Python3.9

Python3.9

Conda
Python

Python 是一种高级、解释型、通用的编程语言,以其简洁易读的语法而闻名,适用于广泛的应用,包括Web开发、数据分析、人工智能和自动化脚本

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值