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 线程传参:target与args参数
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 线程锁:Lock与RLock
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 进程间通信:Pipe与Queue的使用
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 共享内存:Value与Array的使用
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 进程同步:Lock、Event与Semaphore
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 协程与多线程、多进程的结合
协程可以与多线程和多进程结合使用,以实现更高效的并发编程模型。
这些代码示例提供了threading和multiprocessing模块的使用,以及异步编程和协程的基本概念和应用。您可以在本地环境中执行这些代码来验证输出。
两者的区别
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. 通信
- 线程: 线程之间可以通过共享变量、队列等机制进行通信。
- 进程: 进程之间需要使用如
Pipe、Queue等进程间通信(IPC)机制来交换数据。
7. 异常处理
- 线程: 如果一个线程遇到未处理的异常,整个进程可能会崩溃。
- 进程: 如果一个进程崩溃,其他进程通常不受影响,这提高了程序的整体稳定性。
总的来说,选择使用threading还是multiprocessing取决于具体的应用场景和需求。对于需要大量计算和并行处理的任务,multiprocessing可能是更好的选择。而对于需要频繁I/O操作和协作的任务,threading可能更加适合。