lambda 函数中的按引用传递,多线程 C++
pass-by-reference in lambda function, multithreading c++
下面的代码应该测试 运行sin 和 cos 函数在不同线程数下的时间。我正在为一个 运行 时间非常重要的项目写这篇文章,这是一项可行性研究,多线程是否会足够减少 运行 时间。
我们的想法是传递不同的 SAMPLE_SIZE 和 NUM_THREADS,看看它如何影响 运行time。
问题:输出不是我预期的那样。
- void 函数中的 ID cos_sin_multiplication 始终递增 1。所以我得到 (ID:1 ... ID:NUM_THREADS+1) 而不是 (ID:0 ... ID:NUM_THREADS).
- 当我 运行 具有 2/3/4 个线程的代码时,我遇到了分段错误。
- 当我 运行 有 7 个或更多线程时,几个 ID 更改为 NUM_THREADS。
- cos_out[0]的输出总是0
这里是 NUM_THREADS = 8 和 SAMPLE_SIZE = 100'000 的示例输出。
Initiate Thread: 0 with 12500 datapoints.
Initiate Thread: 1 with 12500 datapoints.
Initiate Thread: 2 with 12500 datapoints.
Initiate Thread: 3 with 12500 datapoints.
Initiate Thread: 4 with 12500 datapoints.
Initiate Thread: 5 with 12500 datapoints.
Initiate Thread: 6 with 12500 datapoints.
Initiate Thread: 7 with 12500 datapoints.
ID: 4: sin: 0.861292 cos: -1.72477
ID: 8: sin: -56.1798 cos: 55.4332
ID: 8: sin: -68.1969 cos: 51.9351
ID: 3: sin: 0.861292 cos: -1.72477
ID: 2: sin: 0.861292 cos: -1.72477
ID: 1: sin: 0.861292 cos: -1.72477
ID: 8: sin: -61.1793 cos: 58.8878
ID: 8: sin: -64.8086 cos: 59.5946
The execution took: 0.004465 seconds.
ID: 0: sin: 59.5946 cos: 0
ID: 1: sin: 0.861292 cos: -1.72477
ID: 2: sin: 0.861292 cos: -1.72477
ID: 3: sin: 0.861292 cos: -1.72477
ID: 4: sin: 0.861292 cos: -1.72477
ID: 5: sin: 0 cos: 0
ID: 6: sin: 0 cos: 0
ID: 7: sin: 0 cos: 0
谁能指出我正确的方向?
//Multithreaded Cosnius and Sinus Calculations Benchmark
// Calculate a sample of Cosinus and Sinus with different numbers of Threads
// to determine the runtime gain for different number of threads
#include <math.h>
#include <iostream>
#include <fstream>
#include <thread>
#include <mutex>
#include <chrono>
#include <vector>
#define NUM_THREADS 3
#define SAMPLE_SIZE 2000000
#define PI 3.1415
float diff_time;
std::ofstream calc_speed;
std::mutex out_guard;
void cos_sin_multiplication(int id, int sample, float theta, float& value, float& sin_out, float& cos_out){
for (int j = 0; j < sample; j++){
sin_out += sin(PI*theta);
cos_out += cos(PI*theta);
theta += 0.1;
}
out_guard.lock();
std::cout << "ID: " << id << ": sin: " << sin_out << " cos: " << cos_out << "\n";
out_guard.unlock();
}
int main(){
auto start_time = std::chrono::system_clock::now();
std::vector<std::thread> Threads;
int64_t sample_per_thread;
int mod_sample_per_thread = SAMPLE_SIZE%NUM_THREADS;
float value[SAMPLE_SIZE];
float theta = 0.0;
float cos_out[NUM_THREADS];
float sin_out[NUM_THREADS];
for(int i = 0; i < NUM_THREADS; i++){
cos_out[i] = 0.0;
sin_out[i] = 0.0;
}
for(int i = 0; i < NUM_THREADS; i++){
if (i < mod_sample_per_thread){
sample_per_thread = SAMPLE_SIZE/NUM_THREADS + 1;
}
else{
sample_per_thread = SAMPLE_SIZE/NUM_THREADS;
}
out_guard.lock();
std::cout << "Initiate Thread: " << i <<" with "<< sample_per_thread << " datapoints." << "\n";
out_guard.unlock();
Threads.emplace_back([&](){cos_sin_multiplication(i, sample_per_thread, theta, value[0], sin_out[i], cos_out[i]);});
}
for(auto& t: Threads){
t.join();
}
auto end_time = std::chrono::system_clock::now();
std::chrono::duration<double> diff_time = end_time - start_time;
out_guard.lock();
std::cout << "The execution took: " << diff_time.count() << " seconds. \n";
out_guard.unlock();
for(int i = 0; i < NUM_THREADS; i++){
out_guard.lock();
std::cout << "ID: " << i << ": sin: " << sin_out[i] << " cos: " << cos_out[i] << "\n";
out_guard.unlock();
}
return 0;
}
解法:
将 [&] 替换为 [&, i=i, sample_per_thread=sample_per_thread] s.t。只有需要通过引用传递的东西才通过引用传递。
Threads.emplace_back([&](){cos_sin_multiplication(i, sample_per_thread, theta, value[0], sin_out[i], cos_out[i]);});
}
C++ 无法保证执行线程实际开始执行此闭包的确切时间。您唯一可以依赖的是,这将在构建新的 std::thread
对象(作为 emplace 的一部分)后的某个时刻发生。为了使它正常工作,这远不是必须发生的事情。唯一一切正常的情况是执行线程开始执行闭包,并在 before 之前评估函数调用的所有参数父执行线程立即迭代 for
循环。机会不大。
因此,除了所有其他错误之外,sample_per_thread
也将是为它计算的最后一个值。
完全有可能在 for
循环结束后,您的所有执行线程将最终结束执行此闭包,并评估通过引用捕获的所有参数,并且 i
已被销毁,使一切成为未定义的行为。
即使一些执行线程设法唤醒并早一点闻到咖啡的味道,无论如何,您仍然无法保证 sample_per_thread
会是在 sample_per_thread
之前为其计算的值=11=] 对象被构建。实际上,这几乎可以保证至少某些执行线程将获得 sample_per_thread
的 captured-by-reference 值(在计算下一个执行线程的表面消耗后)。
换句话说,这里没有任何东西可以正常工作,因为所有内容都是通过引用捕获的。
下面的代码应该测试 运行sin 和 cos 函数在不同线程数下的时间。我正在为一个 运行 时间非常重要的项目写这篇文章,这是一项可行性研究,多线程是否会足够减少 运行 时间。
我们的想法是传递不同的 SAMPLE_SIZE 和 NUM_THREADS,看看它如何影响 运行time。
问题:输出不是我预期的那样。
- void 函数中的 ID cos_sin_multiplication 始终递增 1。所以我得到 (ID:1 ... ID:NUM_THREADS+1) 而不是 (ID:0 ... ID:NUM_THREADS).
- 当我 运行 具有 2/3/4 个线程的代码时,我遇到了分段错误。
- 当我 运行 有 7 个或更多线程时,几个 ID 更改为 NUM_THREADS。
- cos_out[0]的输出总是0
这里是 NUM_THREADS = 8 和 SAMPLE_SIZE = 100'000 的示例输出。
Initiate Thread: 0 with 12500 datapoints.
Initiate Thread: 1 with 12500 datapoints.
Initiate Thread: 2 with 12500 datapoints.
Initiate Thread: 3 with 12500 datapoints.
Initiate Thread: 4 with 12500 datapoints.
Initiate Thread: 5 with 12500 datapoints.
Initiate Thread: 6 with 12500 datapoints.
Initiate Thread: 7 with 12500 datapoints.
ID: 4: sin: 0.861292 cos: -1.72477
ID: 8: sin: -56.1798 cos: 55.4332
ID: 8: sin: -68.1969 cos: 51.9351
ID: 3: sin: 0.861292 cos: -1.72477
ID: 2: sin: 0.861292 cos: -1.72477
ID: 1: sin: 0.861292 cos: -1.72477
ID: 8: sin: -61.1793 cos: 58.8878
ID: 8: sin: -64.8086 cos: 59.5946
The execution took: 0.004465 seconds.
ID: 0: sin: 59.5946 cos: 0
ID: 1: sin: 0.861292 cos: -1.72477
ID: 2: sin: 0.861292 cos: -1.72477
ID: 3: sin: 0.861292 cos: -1.72477
ID: 4: sin: 0.861292 cos: -1.72477
ID: 5: sin: 0 cos: 0
ID: 6: sin: 0 cos: 0
ID: 7: sin: 0 cos: 0
谁能指出我正确的方向?
//Multithreaded Cosnius and Sinus Calculations Benchmark
// Calculate a sample of Cosinus and Sinus with different numbers of Threads
// to determine the runtime gain for different number of threads
#include <math.h>
#include <iostream>
#include <fstream>
#include <thread>
#include <mutex>
#include <chrono>
#include <vector>
#define NUM_THREADS 3
#define SAMPLE_SIZE 2000000
#define PI 3.1415
float diff_time;
std::ofstream calc_speed;
std::mutex out_guard;
void cos_sin_multiplication(int id, int sample, float theta, float& value, float& sin_out, float& cos_out){
for (int j = 0; j < sample; j++){
sin_out += sin(PI*theta);
cos_out += cos(PI*theta);
theta += 0.1;
}
out_guard.lock();
std::cout << "ID: " << id << ": sin: " << sin_out << " cos: " << cos_out << "\n";
out_guard.unlock();
}
int main(){
auto start_time = std::chrono::system_clock::now();
std::vector<std::thread> Threads;
int64_t sample_per_thread;
int mod_sample_per_thread = SAMPLE_SIZE%NUM_THREADS;
float value[SAMPLE_SIZE];
float theta = 0.0;
float cos_out[NUM_THREADS];
float sin_out[NUM_THREADS];
for(int i = 0; i < NUM_THREADS; i++){
cos_out[i] = 0.0;
sin_out[i] = 0.0;
}
for(int i = 0; i < NUM_THREADS; i++){
if (i < mod_sample_per_thread){
sample_per_thread = SAMPLE_SIZE/NUM_THREADS + 1;
}
else{
sample_per_thread = SAMPLE_SIZE/NUM_THREADS;
}
out_guard.lock();
std::cout << "Initiate Thread: " << i <<" with "<< sample_per_thread << " datapoints." << "\n";
out_guard.unlock();
Threads.emplace_back([&](){cos_sin_multiplication(i, sample_per_thread, theta, value[0], sin_out[i], cos_out[i]);});
}
for(auto& t: Threads){
t.join();
}
auto end_time = std::chrono::system_clock::now();
std::chrono::duration<double> diff_time = end_time - start_time;
out_guard.lock();
std::cout << "The execution took: " << diff_time.count() << " seconds. \n";
out_guard.unlock();
for(int i = 0; i < NUM_THREADS; i++){
out_guard.lock();
std::cout << "ID: " << i << ": sin: " << sin_out[i] << " cos: " << cos_out[i] << "\n";
out_guard.unlock();
}
return 0;
}
解法: 将 [&] 替换为 [&, i=i, sample_per_thread=sample_per_thread] s.t。只有需要通过引用传递的东西才通过引用传递。
Threads.emplace_back([&](){cos_sin_multiplication(i, sample_per_thread, theta, value[0], sin_out[i], cos_out[i]);});
}
C++ 无法保证执行线程实际开始执行此闭包的确切时间。您唯一可以依赖的是,这将在构建新的 std::thread
对象(作为 emplace 的一部分)后的某个时刻发生。为了使它正常工作,这远不是必须发生的事情。唯一一切正常的情况是执行线程开始执行闭包,并在 before 之前评估函数调用的所有参数父执行线程立即迭代 for
循环。机会不大。
因此,除了所有其他错误之外,sample_per_thread
也将是为它计算的最后一个值。
完全有可能在 for
循环结束后,您的所有执行线程将最终结束执行此闭包,并评估通过引用捕获的所有参数,并且 i
已被销毁,使一切成为未定义的行为。
即使一些执行线程设法唤醒并早一点闻到咖啡的味道,无论如何,您仍然无法保证 sample_per_thread
会是在 sample_per_thread
之前为其计算的值=11=] 对象被构建。实际上,这几乎可以保证至少某些执行线程将获得 sample_per_thread
的 captured-by-reference 值(在计算下一个执行线程的表面消耗后)。
换句话说,这里没有任何东西可以正常工作,因为所有内容都是通过引用捕获的。