进程(process)指的是正在运行的程序的实例,当我们执行某个程序时,进程就被操作系统创建了。而线程(thread)则包含于进程之中,是操作系统能够进行运算调度的最小单元,多个线程可以同处一个进程中,且同时处理不同的任务。一条进程中可以并发多个线程,而同一条线程将共享该进程中的全部系统资源。
每个进程都有自己独立的地址空间、内存和数据栈,因此进程之间通讯不方便,所以需使用用进程间通讯(InterProcess Communication, IPC)。而同一个进程中的线程共享资源,因此线程间通讯非常方便,只需注意数据同步与互斥的问题。
Python支持多线程编程和多进程编程。在本教程中,我们将学习有关Python多线程编程的基础知识、线程同步、线程池以及如何使用多进程。
1. 多线程基础
线程是进程内的执行单元,每个进程都至少有一个线程。Python标准库提供了thread模块和threading模块来支持线程编程。在Python3中,thread模块已经改名为_thread
,并在_thread
基础上开发了更为强大的threading
模块,因此我们可以使用threading.Thread
类创建线程对象,并通过start()
方法启动线程。
import threading
def worker():
print("I am a thread.")
t = threading.Thread(target=worker)
t.start()
在上面的代码中,我们创建了一个名为worker的函数,它是线程的工作内容。我们使用threading.Thread类创建了一个线程对象t,并将worker作为参数传递给了Thread构造函数。然后,我们调用t.start()方法来启动线程。当我们运行这段代码时,会看到以下输出:
I am a thread.
另外,我们也可以通过继承Thread类并重写run
方法来实现线程的创建
import threading
class MyThread(threading.Thread):
def __init__(self, n):
self.n = n
super().__init__() # 调用父类函数初始化线程
def run(self):
print('线程:', self.n)
for i in range(1,3):
t = MyThread(i)
t.start()
结果应该输出打印出:
线程1
线程2
2. 多线程同步
当多个线程同时访问共享资源时,可能会发生竞态条件。为避免这种情况,我们需要使用线程同步机制。Python提供了锁(Lock)、信号量(Semaphore)、事件(Event)等机制来实现线程同步。
下面是一个使用锁来确保线程同步的示例:
import threading
lock = threading.Lock()
count = 0
def worker():
global count
lock.acquire()
try:
count += 1
finally:
lock.release()
threads = []
for i in range(10):
t = threading.Thread(target=worker)
threads.append(t)
for t in threads:
t.start()
for t in threads:
t.join()
print(count)
在上面的代码中,我们定义了一个全局变量count
和一个锁对象lock
。worker
函数是每个线程的工作内容,它通过lock
来确保count
的修改是原子性的。我们创建了10个线程并启动它们,等待所有线程执行完毕后输出count
的值。当我们运行这段代码时,会看到以下输出:
10
如果把锁去掉后,count
的值会是几呢?动手试试吧!会有意向不到的结果哦~
3. 线程池
线程池可以用来提高代码的性能,在需要大量线程处理任务时,可以使用线程池来减少线程的创建和销毁次数,提高线程的复用率。
Python
标准库提供了concurrent.futures
模块来支持线程池编程。我们可以使用ThreadPoolExecutor
类来创建和管理线程池。
下面是一个使用线程池来处理任务的示例:
from concurrent.futures import ThreadPoolExecutor
def worker(num):
return num * num
executor = ThreadPoolExecutor(max_workers=4)
results = []
for i in range(10):
result = executor.submit(worker, i)
results.append(result)
for result in results:
print(result.result())
在上面的代码中,我们定义了一个worker
函数用来处理任务。我们创建了一个ThreadPoolExecutor
对象executor
,并将最大工作线程数设置为4
。然后,我们使用submit
方法提交10个任务给executor
,并将结果保存在results
列表中。最后,我们遍历results
列表并输出每个任务的结果。
看到这些个熟悉的单词…恍惚间有种写java代码的感觉。
4. 多进程编程
进程是计算机中程序执行的基本单位,而线程则是进程中执行代码的单位。多进程编程就是利用多个独立运行的进程来完成任务。相比单进程,多进程有以下优点:
- 提高了程序的并发性能。
- 可以更好地利用多核CPU。
- 能够处理更大的数据集。
在进行多进程编程时,需要注意进程之间的通信和同步问题。
5. Python实现多进程编程
Python标准库中的multiprocessing
模块提供了实现多进程编程的工具。其中常用的方法包括:
- Process:创建一个新进程。
- Pool:创建一组进程池。
- Queue:进程之间的消息队列。
下面是一个简单的示例,展示如何使用multiprocessing
模块创建子进程:
import multiprocessing
def worker():
"""子进程要执行的任务"""
print('子进程正在运行')
if __name__ == '__main__':
p = multiprocessing.Process(target=worker)
p.start()
print('主进程已经结束')
在这个例子中,我们创建一个新的进程,并将其target
属性指向worker
函数。然后使用start
方法启动该进程。注意到在Windows系统中,需要将启动进程的代码放在if __name__ == '__main__':
语句内,以避免出现一些问题。
5.1 Python进程池
import multiprocessing
def worker(num):
"""子进程要执行的任务"""
print(f'子进程{num}正在运行')
if __name__ == '__main__':
with multiprocessing.Pool(processes=4) as pool:
results = pool.map(worker, range(4))
print(results)
在这个例子中,我们使用了进程池来管理多个子进程。首先创建一个Pool对象,其中processes参数指定了进程池的大小。然后使用map方法提交任务,该方法会自动分配任务给空闲的子进程并返回结果。最后打印输出结果。
5.2 在进程之间进行通信
import multiprocessing
def writer(q):
"""写入数据"""
for i in range(10):
q.put(i)
def reader(q):
"""读取数据"""
while True:
item = q.get()
if item is None:
break
print(item)
if __name__ == '__main__':
q = multiprocessing.Queue()
# 启动子进程
p1 = multiprocessing.Process(target=writer, args=(q,))
p2 = multiprocessing.Process(target=reader, args=(q,))
p1.start()
p2.start()
# 等待子进程结束
p1.join()
q.put(None)
p2.join()
在这个例子中,我们创建了一个消息队列,并通过Queue对象将其传递给两个子进程。一个子进程负责将数据写入队列中,而另一个子进程则从队列中读取数据并输出到屏幕上。注意到在程序末尾我们向队列中添加了None元素,以便通知读取数据的子进程结束循环。
6.多进程计算圆周率
下面是使用Python多进程和蒙特卡罗(Monte Carlo)方法估计圆周率的示例代码:
import random
from multiprocessing import Pool
def estimate_pi(n):
num_points_inside_circle = 0
for _ in range(n):
x = random.uniform(-1, 1)
y = random.uniform(-1, 1)
if x**2 + y**2 <= 1:
num_points_inside_circle += 1
return 4 * num_points_inside_circle / n
if __name__ == '__main__':
num_processes = 4
num_samples = int(1e8)
with Pool(num_processes) as p:
results = p.map(estimate_pi, [int(num_samples/num_processes)] * num_processes)
print(sum(results)/num_processes)
该代码使用random
模块中的uniform
函数生成在$[-1, 1]$范围内均匀分布的随机数,并统计落入以原点为圆心,半径为1的圆中的点数。最后,将所有子进程的结果相加并除以进程数以得到圆周率的估计值。我算出的结果是3.1416664800000005
, 你们呢?
其中,Pool(num_processes)
创建一个具有num_processes
个进程的进程池,p.map(estimate_pi, [int(num_samples/num_processes)] * num_processes)
在每个进程上调用estimate_pi
函数,并将输入参数设为所需的样本数量的四分之一,然后返回其结果。
7.总结
在Python多线程与进程编程中,我们不仅要理解和掌握的关于threading
模块和multiprocessing
模块如何使用,更要掌握的是线程和进程之间的区别和联系以及线程通信、进程通信的方式。
上一篇教程:Python基础教程:正则表达式
Python提供了丰富的多线程和多进程编程工具,使我们能够更有效地利用计算机的多核心处理能力。线程同步和线程池等同步原语可以帮助我们避免竞争条件,并提高程序性能。使用好多线程和多进程,可以使Python编程变得更加高效和灵活。Python爬虫应用中,经常会大量使用线程和进程来提升抓取效率哦
[…] 上一篇教程:Python基础教程:多线程编程 […]