在C++标准库中,std::for_each
是一个用于遍历容器或可迭代序列并对每个元素执行特定操作的强大工具。传统的std::for_each
是顺序执行的,即它会按照元素在序列中的顺序,逐个应用函数对象或lambda表达式。然而,随着多线程编程的普及和硬件性能的提升,越来越多的开发者开始寻求并发执行以提高程序性能。
幸运的是,从C++17开始,标准库引入了执行策略(Execution Policies),使得一些算法,包括std::for_each
,可以在多个线程上并行执行。通过使用特定的执行策略,如std::execution::par
,我们可以指示算法并行处理元素,从而利用多核处理器的优势。
普通循环
std::for_each
是 C++ 标准库 <algorithm>
中的一个函数,它用于对容器(如数组、向量、列表等)或任何可迭代序列中的每个元素执行指定的操作。这个函数接收三个参数:一个迭代器范围(表示序列的开始和结束),以及一个函数对象或 lambda 表达式,该函数对象或 lambda 表达式将应用于序列中的每个元素。
基本语法
template< class InputIt, class UnaryFunction >
UnaryFunction for_each( InputIt first, InputIt last, UnaryFunction f );
first
和last
是输入迭代器,表示要遍历的序列的开始和结束。f
是一个一元函数对象或 lambda 表达式,它将应用于序列中的每个元素。
代码示例
下面是一个简单的例子,演示如何使用 std::for_each
来打印向量中的所有元素:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
std::for_each(v.begin(), v.end(), [](int n) {
std::cout << n << ' ';
});
std::cout << std::endl;
return 0;
}
输出将是:
1 2 3 4 5
在这个例子中,使用了一个 lambda 表达式作为 std::for_each
的第三个参数。这个 lambda 表达式接收一个整数 n
并打印它。std::for_each
将这个 lambda 表达式应用于向量 v
中的每个元素。
并发执行
std::for_each
本身并不直接支持并发执行。传统的 std::for_each
函数是顺序执行的,它按照迭代器指定的范围逐个处理元素,而不会并行地在多个线程或处理器上同时执行。这意味着对于每个元素,std::for_each
会按照它们在序列中的顺序,依次调用提供的函数对象或 lambda 表达式。
然而,C++17 引入了执行策略(Execution Policies),这使得一些算法(包括 std::for_each
)可以并行执行。通过使用特定的执行策略,如 std::execution::par
,你可以指示算法在多个线程上并行处理元素。
基本语法
template< class InputIt, class UnaryFunction >
UnaryFunction for_each(std::execution::par, InputIt first, InputIt last, UnaryFunction f );
注意添加头文件#include <execution> // 包含执行策略的头文件
代码示例
#include <iostream>
#include <vector>
#include <algorithm>
#include <execution> // 包含执行策略的头文件
int main() {
std::vector<int> v = {1, 2, 3, 4, 5};
// 使用 std::execution::par 执行策略来指示并行执行
std::for_each(std::execution::par, v.begin(), v.end(), [](int& n) {
n *= 2; // 将每个元素乘以 2
});
// 打印修改后的向量
for (int n : v) {
std::cout << n << ' ';
}
std::cout << std::endl;
return 0;
}
在这个例子中,使用了 std::execution::par
执行策略来告诉 std::for_each
在多个线程上并行执行。这意味着向量的元素可能会被多个线程同时处理,从而提高了执行效率(尤其是在处理大量数据或进行复杂操作时)。然而,需要注意的是,并行执行并不保证元素处理的顺序与它们在序列中的顺序一致。
请注意,并行执行的效果取决于具体的编译器实现和运行时环境。不是所有的编译器都支持并行算法,也不是所有的系统都能够有效地利用并行执行带来的优势。此外,并行执行可能会引入额外的同步和通信开销,因此在某些情况下,顺序执行可能实际上比并行执行更快。
当使用 std::for_each
的并行版本时,由于执行是并发的,输出可能会受到线程调度、数据竞争(尽管在这个例子中不太可能,因为每个元素都是独立修改的)和其他并发问题的影响。但是,在你的例子中,每个元素的操作是独立的(即,将每个元素乘以2),因此不会存在数据竞争。
由于并行执行不保证元素的处理顺序,因此最终向量 v
中的元素顺序可能仍然保持不变,每个元素都被乘以了2。然而,重要的是要理解,虽然每个元素最终的值是正确的,但元素被乘以2的顺序在并行执行中可能是不确定的。
所以,对于你给出的例子,可能的输出仍然是:
2 4 6 8 10
这个输出表示每个元素都被成功地乘以了2,但是由于并行执行,不能确定这个操作是按顺序进行的。换句话说,尽管输出值本身是正确的,但达到这个输出值的中间过程可能涉及多个线程同时工作。
在实际应用中,如果你需要处理具有依赖关系的元素(即,一个元素的计算依赖于另一个元素的结果),那么你应该避免使用并行版本的 std::for_each
或其他并行算法,因为这样的依赖关系可能导致错误的结果。在你的例子中,因为每个元素的计算是独立的,所以并行执行是安全的。