深入理解并行与并发:C++/Python实例详解
万古教员有名言,自信人生二百年。
个人主页:oioihoii
喜欢内容的话欢迎关注、点赞、收藏!感谢支持,祝大家祉猷并茂,顺遂无虞!
并行(Parallelism)和并发(Concurrency)是计算机科学中两个重要的概念,尤其在多线程和多进程编程中。虽然这两个术语经常被混用,但它们实际上有着不同的含义和应用场景。下面我们将详细探讨这两个概念的定义、区别、应用场景以及实例。
1. 定义
并发(Concurrency)
并发是指在同一时间段内处理多个任务的能力。并发并不一定意味着这些任务是同时执行的,而是指多个任务在逻辑上是同时进行的。并发可以通过时间分片(time-slicing)来实现,即在一个处理器上快速切换任务,使得用户感觉到多个任务在同时进行。
特点:
- 任务可以在同一时间段内交替执行。
- 适用于 I/O 密集型任务(如网络请求、文件读写等)。
- 主要关注任务的管理和调度。
并行(Parallelism)
并行是指在同一时刻同时执行多个任务。并行通常依赖于多核处理器或多台计算机,能够真正实现任务的同时执行。并行计算可以显著提高程序的执行效率,尤其是在 CPU 密集型任务中。
特点:
- 任务在物理上同时执行。
- 适用于 CPU 密集型任务(如复杂计算、数据处理等)。
- 主要关注任务的分解和执行。
2. 区别
特性 | 并发(Concurrency) | 并行(Parallelism) |
---|---|---|
定义 | 逻辑上同时处理多个任务 | 物理上同时执行多个任务 |
任务执行方式 | 任务交替执行(时间分片) | 任务同时执行(多核或多处理器) |
适用场景 | I/O 密集型任务 | CPU 密集型任务 |
实现方式 | 通过线程、协程、事件循环等 | 通过多线程、多进程、分布式计算等 |
复杂性 | 任务管理和调度较复杂 | 任务分解和同步较复杂 |
3. 应用场景
并发的应用场景
- Web 服务器:处理多个客户端请求,通常使用异步 I/O 或线程池来实现并发。
- 用户界面:在 GUI 应用中,主线程负责界面响应,而后台线程处理耗时操作,保持界面流畅。
- 网络爬虫:同时发起多个网络请求,快速抓取数据。
并行的应用场景
- 科学计算:如天气模拟、分子动力学等,需要大量计算的任务可以分解为多个子任务并行执行。
- 图像处理:对大图像进行滤镜处理时,可以将图像分成多个部分并行处理。
- 大数据处理:使用 MapReduce 等框架对大规模数据集进行并行计算。
4. Python中的并发与并行
并发示例
假设我们有一个简单的程序,需要从多个 URL 下载数据。我们可以使用 Python 的 asyncio
库来实现并发下载:
import asyncio
import aiohttp
async def fetch(url):
async with aiohttp.ClientSession() as session:
async with session.get(url) as response:
return await response.text()
async def main(urls):
tasks = [fetch(url) for url in urls]
return await asyncio.gather(*tasks)
urls = ['http://example.com', 'http://example.org', 'http://example.net']
asyncio.run(main(urls))
在这个例子中,fetch
函数是异步的,多个 URL 的下载是并发进行的,但并不一定是同时执行的。
并行示例
使用 Python 的 multiprocessing
模块来实现并行计算:
from multiprocessing import Pool
def square(n):
return n * n
if __name__ == '__main__':
numbers = [1, 2, 3, 4, 5]
with Pool(processes=5) as pool:
results = pool.map(square, numbers)
print(results)
在这个例子中,square
函数会在多个进程中并行执行,真正实现了同时计算多个数字的平方。
举例说明
- 并发:想象一下你在厨房里做饭,同时切菜、煮汤和烤面包。虽然你在同一时间段内做了这些事情,但实际上你可能在不同的时间段内完成了每一项任务。
- 并行:如果你有多个厨师在厨房里,每个厨师负责不同的任务(一个切菜,一个煮汤,一个烤面包),那么这些任务就是并行执行的。
5. C++中的并发与并行
C++11引入了对多线程的支持,使得并发和并行编程变得更加容易。下面我们将通过示例代码来展示这两个概念。
并发示例
在这个示例中,我们将创建多个线程来模拟并发执行的任务。每个线程将打印一条消息,模拟一个耗时的操作。
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
void task(int id) {
std::cout << "Task " << id << " is starting.\n";
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
std::cout << "Task " << id << " is completed.\n";
}
int main() {
std::vector<std::thread> threads;
// 创建多个线程
for (int i = 0; i < 5; ++i) {
threads.emplace_back(task, i);
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
std::cout << "All tasks are completed.\n";
return 0;
}
代码解析
- 我们定义了一个
task
函数,模拟一个耗时的操作。 - 在
main
函数中,我们创建了5个线程,每个线程执行task
函数。 - 使用
std::this_thread::sleep_for
来模拟每个任务的耗时。 - 最后,我们使用
join
方法等待所有线程完成。
并行示例
在这个示例中,我们将使用C++的线程库来实现真正的并行计算。我们将计算一组数字的平方,并行处理这些计算。
#include <iostream>
#include <thread>
#include <vector>
#include <chrono>
void calculate_square(int number, int& result) {
std::this_thread::sleep_for(std::chrono::seconds(1)); // 模拟耗时操作
result = number * number;
std::cout << "Square of " << number << " is " << result << ".\n";
}
int main() {
std::vector<std::thread> threads;
std::vector<int> results(5);
std::vector<int> numbers = {1, 2, 3, 4, 5};
// 创建多个线程进行并行计算
for (size_t i = 0; i < numbers.size(); ++i) {
threads.emplace_back(calculate_square, numbers[i], std::ref(results[i]));
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
std::cout << "All calculations are completed.\n";
return 0;
}
代码解析
- 我们定义了一个
calculate_square
函数,计算给定数字的平方。 - 在
main
函数中,我们创建了5个线程,每个线程处理一个数字的平方计算。 - 使用
std::ref
将结果传递给线程,以便线程能够修改主线程中的结果。 - 最后,我们等待所有线程完成。
6. 总结
- 并发和并行是处理多个任务的两种不同方式,前者强调任务的管理和调度,后者强调任务的同时执行。
- 理解这两个概念对于设计高效的程序和系统至关重要,尤其是在现代多核处理器和分布式计算环境中。
- 在实际应用中,选择并发还是并行取决于任务的性质(I/O 密集型还是 CPU 密集型)以及系统的架构。
进一步探讨交流以及更多惊喜请关注公众号联系我!再次欢迎关注、点赞、收藏,系列内容可以点击专栏目录订阅,感谢支持,祝大家祉猷并茂,顺遂无虞!
若将文章用作它处,请一定注明出处,商用请私信联系我!