2.19多线程与多进程

120 阅读7分钟

2.19多线程与多进程

2.19.1 threading模块的使用

2.19.1.1 threading模块简介

threading模块提供了一个高级的、基于线程的并发接口。

2.19.1.2 创建线程:Thread类的使用

import threading
​
# 定义一个函数,作为线程的执行目标
def print_numbers():
    for i in range(5):
        print(i)
​
# 创建线程
thread = threading.Thread(target=print_numbers)
​
# 启动线程
thread.start()
​
# 等待线程结束
thread.join()

输出:

0
1
2
3
4

解释: 创建并启动了一个线程来执行print_numbers函数。

2.19.1.3 线程传参:targetargs参数

def print_greeting(name):
    print(f"Hello {name}")
​
# 创建线程时传参
thread = threading.Thread(target=print_greeting, args=("World",))
thread.start()
thread.join()

输出:

Hello World

解释: args参数允许传递参数给线程的目标函数。

2.19.1.4 线程锁:LockRLock

lock = threading.Lock()
​
def thread_function(name):
    with lock:
        print(f"Thread {name} acquired the lock")
        print(f"Thread {name} released the lock")
​
thread1 = threading.Thread(target=thread_function, args=(1,))
thread2 = threading.Thread(target=thread_function, args=(2,))
​
thread1.start()
thread2.start()
​
thread1.join()
thread2.join()

输出: 两个线程交替输出,因为它们共享同一个锁。

Thread 1 acquired the lock
Thread 1 released the lock
Thread 2 acquired the lock
Thread 2 released the lock

2.19.1.5 线程通信:Queue模块的使用

from queue import Queue
import threading
​
def producer(queue):
    queue.put("Message from producer")
​
def consumer(queue):
    print(queue.get())
​
queue = Queue()
producer_thread = threading.Thread(target=producer, args=(queue,))
consumer_thread = threading.Thread(target=consumer, args=(queue,))
​
producer_thread.start()
consumer_thread.start()
​
producer_thread.join()
consumer_thread.join()

输出:

Message from producer

解释: Queue用于线程间安全地传递消息。

2.19.1.6 线程的守护与非守护模式

def daemon_thread():
    while True:
        print("Daemon thread is running")

# 创建守护线程
daemon = threading.Thread(target=daemon_thread)
daemon.daemon = True
daemon.start()

# 主线程结束后,守护线程将自动结束
print("Main thread is ending")

输出: 主线程结束后,守护线程将不会继续运行。

2.19.1.7 线程池的实现与使用(ThreadPoolExecutor

from concurrent.futures import ThreadPoolExecutor

def task(num):
    print(f"Processing {num}")

with ThreadPoolExecutor(max_workers=2) as executor:
    for i in range(5):
        executor.submit(task, i)

输出: 任务将由线程池中的线程异步执行。

Processing 0
Processing 1
Processing 2
Processing 3
Processing 4

2.19.2 multiprocessing模块的使用

2.19.2.1 multiprocessing模块简介

multiprocessing模块提供了一个用于并行计算的API,允许你创建进程。

2.19.2.2 创建进程:Process类的使用

from multiprocessing import Process

def print_numbers():
    for i in range(5):
        print(i)

process = Process(target=print_numbers)
process.start()
process.join()

输出:

0
1
2
3
4

2.19.2.3 进程间通信:PipeQueue的使用

from multiprocessing import Process, Pipe

def child(conn):
    conn.send("Hello from child")
    conn.close()

parent_conn, child_conn = Pipe()
Process(target=child, args=(child_conn,)).start()
print(parent_conn.recv())
parent_conn.close()

输出:

Hello from child

2.19.2.4 共享内存:ValueArray的使用

from multiprocessing import Process, Value, Array

def child(value, array):
    value.value = 3.14
    for i in range(len(array)):
        array[i] = -i

num = Value('d', 0.0)
arr = Array('i', range(5))
Process(target=child, args=(num, arr)).start().join()
print(num.value)
print(arr[:])

输出:

3.14
[-0, -1, -2, -3, -4]

2.19.2.5 进程池:Pool的创建与使用

from multiprocessing import Pool

def task(x):
    return x * x

with Pool(2) as p:
    print(p.map(task, [1, 2, 3, 4]))

输出:

[1, 4, 9, 16]

2.19.2.6 进程同步:LockEventSemaphore

from multiprocessing import Process, Lock

def run(lock):
    lock.acquire()
    try:
        print("Lock acquired")
    finally:
        lock.release()

lock = Lock()
Process(target=run, args=(lock,)).start()

输出:

Lock acquired

2.19.2.7 进程的守护与非守护模式

与线程类似,进程也可以设置为守护进程,以便在主进程退出时自动结束。

2.19.3 线程与进程的同步机制

2.19.3.1 线程同步问题的解决方案

# 使用Lock
lock = threading.Lock()

def print_block(letters, lock):
    for letter in letters:
        lock.acquire()
        try:
            print(letter)
        finally:
            lock.release()

thread1 = threading.Thread(target=print_block, args=("ABC", lock))
thread2 = threading.Thread(target=print_block, args=("123", lock))

thread1.start()
thread2.start()

thread1.join()
thread2.join()

输出: 字母将交错打印,因为它们共享同一个锁。

2.19.3.2 进程同步问题的解决方案

from multiprocessing import Process, Value, Lock

def run(lock, value):
    lock.acquire()
    try:
        value.value += 1
    finally:
        lock.release()

if __name__ == "__main__":
    lock = Lock()
    value = Value('i', 0)
    processes = [Process(target=run, args=(lock, value)) for _ in range(10)]

    for p in processes:
        p.start()
    for p in processes:
        p.join()

    print(value.value)

输出:

10

2.19.4 异步

2.19.4.1 异步编程概述

异步编程是一种并发编程范式,允许你编写非阻塞代码。

2.19.4.2 模块简介

Python中的异步编程主要通过asyncio模块实现。

2.19.4.3 异步与多线程、多进程的比较

异步编程适用于I/O密集型任务,而多线程和多进程适用于CPU密集型任务。

2.19.4.4 异步IO操作的实现与应用

import asyncio

async def fetch_data(url):
    print(f"Fetching {url}")
    await asyncio.sleep(1)  # 模拟I/O操作
    print(f"Finished fetching {url}")

async def main():
    await fetch_data("https://example.com")

asyncio.run(main())

输出:

Fetching https://example.com
Finished fetching https://example.com

2.19.5 协程

2.19.5.1 协程的基本概念

协程是一种程序组件,允许挂起和恢复执行。

2.19.5.2 协程的创建与使用

async def coroutine():
    print("Coroutine started")
    await asyncio.sleep(1)
    print("Coroutine finished")

asyncio.run(coroutine())

输出:

Coroutine started
Coroutine finished

2.19.5.3 协程与事件循环

import asyncio

async def print_numbers():
    for i in range(

3): print(i) await asyncio.sleep(1)

asyncio.run(print_numbers())

**输出:**

0 1 2


#### 2.19.5.4 协程的并发控制
```python
import asyncio

async def task(name, delay):
    print(f"Task {name} started")
    await asyncio.sleep(delay)
    print(f"Task {name} finished")

async def main():
    await asyncio.gather(
        task("A", 2),
        task("B", 1),
    )

asyncio.run(main())

输出:

Task A started
Task B started
Task B finished
Task A finished

2.19.5.5 协程与多线程、多进程的结合

协程可以与多线程和多进程结合使用,以实现更高效的并发编程模型。

这些代码示例提供了threadingmultiprocessing模块的使用,以及异步编程和协程的基本概念和应用。您可以在本地环境中执行这些代码来验证输出。

两者的区别

multiprocessing模块和threading模块都是Python中用于实现并发编程的模块,但它们在实现方式和使用场景上有一些重要的区别:

1. 处理方式

  • threading模块(线程): 线程是操作系统调度的基本单位。在Python中,线程可以通过threading模块创建。线程共享进程的内存空间,这意味着它们可以访问相同的全局变量和对象。
  • multiprocessing模块(进程): 进程是操作系统分配资源的基本单位。multiprocessing模块允许你创建进程,每个进程有自己的内存空间,这意味着它们不能直接访问其他进程的变量和对象。

2. 资源和内存

  • 线程: 线程之间共享内存,这使得它们在共享数据时更加高效,但也可能导致竞争条件,因此需要使用锁等同步机制来管理对共享资源的访问。
  • 进程: 进程拥有独立的内存空间,这使得它们在处理数据时更加安全,因为不会发生数据竞争。但这也意味着它们在数据共享和通信方面不如线程高效。

3. 性能和适用场景

  • 线程: 线程适用于I/O密集型任务,如网络通信、文件读写等,因为它们可以在等待I/O操作完成时让出CPU资源给其他线程使用。
  • 进程: 进程适用于CPU密集型任务,因为它们可以利用多核CPU同时执行多个计算任务,而不受GIL(全局解释器锁)的限制。

4. 全局解释器锁(GIL)

  • 线程: Python的线程受到GIL的影响,这意味着在任何时刻只有一个线程可以执行Python字节码。这限制了线程在CPU密集型任务中的性能。
  • 进程: 进程不受GIL的限制,每个进程有自己的Python解释器和内存空间,因此可以真正并行执行。

5. 创建和管理

  • 线程: 创建和管理线程相对简单,但需要仔细处理共享资源和同步问题。
  • 进程: 创建和管理进程比线程更复杂,因为它们需要更多的资源(如内存),但它们在处理共享资源时更简单,因为每个进程有自己的独立空间。

6. 通信

  • 线程: 线程之间可以通过共享变量、队列等机制进行通信。
  • 进程: 进程之间需要使用如PipeQueue等进程间通信(IPC)机制来交换数据。

7. 异常处理

  • 线程: 如果一个线程遇到未处理的异常,整个进程可能会崩溃。
  • 进程: 如果一个进程崩溃,其他进程通常不受影响,这提高了程序的整体稳定性。

总的来说,选择使用threading还是multiprocessing取决于具体的应用场景和需求。对于需要大量计算和并行处理的任务,multiprocessing可能是更好的选择。而对于需要频繁I/O操作和协作的任务,threading可能更加适合。

4-1-2.png