C++ 线程参数
介绍
在C++多线程编程中,我们经常需要向线程传递参数,使线程能够处理特定的数据或执行自定义的操作。C++11引入的std::thread
类提供了强大而灵活的方式来创建线程并传递各种类型的参数。本文将详细介绍如何在C++中向线程函数传递参数,包括基本数据类型、引用、指针和类对象等不同类型的参数。
基本参数传递
传递简单参数
最基本的情况是向线程函数传递简单的基本类型参数:
#include <iostream>
#include <thread>
void threadFunction(int x, double y) {
std::cout << "线程内部: x = " << x << ", y = " << y << std::endl;
}
int main() {
// 创建线程并传递两个参数
std::thread t(threadFunction, 10, 20.5);
// 等待线程完成
t.join();
return 0;
}
输出:
线程内部: x = 10, y = 20.5
在上面的示例中,我们创建了一个接受两个参数的线程函数threadFunction
,并在创建线程时传递了这些参数。std::thread
构造函数的第一个参数是线程要执行的函数,后续参数则是传递给该函数的实际参数。
std::thread
构造函数使用完美转发来传递参数,这意味着参数值在传递过程中会被复制或移动到新线程的内存空间。
传递引用参数
使用std::ref和std::cref传递引用
默认情况下,std::thread
会复制所有参数值。如果想传递引用,需要使用std::ref
或std::cref
:
#include <iostream>
#include <thread>
#include <functional> // 为std::ref提供支持
void threadFunction(int& x) {
x = 20; // 修改引用的值
std::cout << "线程内部: x = " << x << std::endl;
}
int main() {
int value = 10;
std::cout << "线程创建前: value = " << value << std::endl;
// 使用std::ref传递引用
std::thread t(threadFunction, std::ref(value));
t.join();
std::cout << "线程结束后: value = " << value << std::endl;
return 0;
}
输出:
线程创建前: value = 10
线程内部: x = 20
线程结束后: value = 20
传递引用给线程时要确保被引用的对象在线程执行期间一直有效。如果对象生命周期结束但线程仍在执行,会导致未定义行为。
传递指针参数
向线程传递指针类似于传递基本类型,但需要注意指针指向的数据的生命周期:
#include <iostream>
#include <thread>
void threadFunction(int* ptr) {
*ptr = 30; // 修改指针指向的值
std::cout << "线程内部: *ptr = " << *ptr << std::endl;
}
int main() {
int value = 10;
std::cout << "线程创建前: value = " << value << std::endl;
// 传递指针
std::thread t(threadFunction, &value);
t.join();
std::cout << "线程结束后: value = " << value << std::endl;
return 0;
}
输出:
线程创建前: value = 10
线程内部: *ptr = 30
线程结束后: value = 30
传递指针时,必须确保指针指向的内存在线程执行期间依然有效。如果指针指向的是局部变量,且线程在函数返回后仍在执行,就会导致严重的内存问题。
传递类成员函数和对象
调用类的成员函数作为线程函数
要在线程中调用类的成员函数,需要传递对象实例和成员函数指针:
#include <iostream>
#include <thread>
class MyClass {
public:
void threadTask(int param) {
std::cout << "在类成员函数中执行线程,参数值: " << param << std::endl;
}
};
int main() {
MyClass obj;
// 使用成员函数创建线程
std::thread t(&MyClass::threadTask, &obj, 100);
t.join();
return 0;
}
输出:
在类成员函数中执行线程,参数值: 100
在上面的示例中,std::thread
构造函数的第一个参数是成员函数指针,第二个参数是对象实例的指针(或引用),之后的参数才是传递给成员函数的实际参数。
传递临时对象和移动语义
使用C++11的移动语义,可以将临时对象高效地传递给线程:
#include <iostream>
#include <thread>
#include <vector>
#include <string>
void threadFunction(std::string str, std::vector<int> vec) {
std::cout << "线程内部: " << str << std::endl;
std::cout << "向量大小: " << vec.size() << std::endl;
}
int main() {
// 传递临时对象
std::thread t(threadFunction,
std::string("Hello from thread"),
std::vector<int>{1, 2, 3, 4, 5});
t.join();
return 0;
}
输出:
线程内部: Hello from thread
向量大小: 5
Lambda表达式与线程参数
Lambda表达式与线程配合使用非常灵活:
#include <iostream>
#include <thread>
int main() {
int x = 10;
// 使用lambda表达式捕获变量
std::thread t([x]() {
std::cout << "在lambda中捕获的值: " << x << std::endl;
});
t.join();
// 引用捕获
int y = 20;
std::thread t2([&y]() {
y = 30; // 修改捕获的引用
std::cout << "在lambda中修改捕获的引用: " << y << std::endl;
});
t2.join();
std::cout << "主线程中的y: " << y << std::endl;
return 0;
}
输出:
在lambda中捕获的值: 10
在lambda中修改捕获的引用: 30
主线程中的y: 30
Lambda表达式可以通过捕获列表捕获外部变量,当与线程一起使用时特别方便。值捕获([x]
)会复制值,而引用捕获([&y]
)则允许修改原始变量。
实际应用案例
并行数据处理
下面是一个使用多线程对向量进行并行求和的实际案例:
#include <iostream>
#include <thread>
#include <vector>
#include <numeric>
#include <functional>
void sumRange(const std::vector<int>& data, size_t start, size_t end, long long& result) {
result = std::accumulate(data.begin() + start, data.begin() + end, 0LL);
}
int main() {
// 创建一个大向量
std::vector<int> bigData(10000000, 1); // 1000万个1
// 分块处理
const size_t numThreads = 4;
const size_t blockSize = bigData.size() / numThreads;
std::vector<std::thread> threads(numThreads);
std::vector<long long> results(numThreads);
// 创建线程并分配工作
for (size_t i = 0; i < numThreads; ++i) {
size_t start = i * blockSize;
size_t end = (i == numThreads - 1) ? bigData.size() : (i + 1) * blockSize;
threads[i] = std::thread(sumRange, std::ref(bigData), start, end, std::ref(results[i]));
}
// 等待所有线程完成
for (auto& t : threads) {
t.join();
}
// 合并结果
long long totalSum = 0;
for (long long partialSum : results) {
totalSum += partialSum;
}
std::cout << "向量中所有元素的总和: " << totalSum << std::endl;
return 0;
}
输出:
向量中所有元素的总和: 10000000
在这个实际案例中,我们将一个大向量分成几个区块,每个线程处理一个区块,最后合并结果。这展示了如何有效地传递引用、范围和数据给不同线程,以实现数据的并行处理。
线程参数传递的注意事项
在使用线程参数时,需要注意以下几点:
-
数据竞争:当多个线程同时访问同一个变量并且至少有一个线程执行写操作时,可能会发生数据竞争,导致未定义行为。使用互斥量或原子操作来避免这种情况。
-
生命周期:确保传递给线程的引用和指针在线程执行期间保持有效。
-
参数复制:默认情况下,参数会被复制到线程的存储空间,对于大型对象可能会影响性能。
-
传递引用:使用
std::ref
或std::cref
来传递引用,否则会发生复制。 -
异常处理:在线程函数中捕获异常,因为未捕获的异常会导致程序终止。
总结
在C++多线程编程中,正确传递参数对于编写健壮的多线程程序至关重要。本文介绍了:
- 基本类型参数的传递
- 使用
std::ref
和std::cref
传递引用参数 - 指针参数的传递和注意事项
- 类成员函数作为线程函数
- 移动语义和临时对象的传递
- Lambda表达式与线程的结合使用
- 实际应用案例展示如何利用线程参数进行并行计算
掌握这些技术可以让你更有效地利用C++的多线程能力,开发出高效、可靠的并发程序。
练习
- 创建一个程序,使用多线程计算斐波那契数列的不同部分,然后合并结果。
- 编写一个程序,在一个线程中修改一个字符串,在另一个线程中读取该字符串,并使用适当的同步机制确保安全访问。
- 实现一个并行的图像处理程序,每个线程处理图像的一部分(例如,应用滤镜或调整亮度)。
额外资源
- C++ Reference: std::thread
- C++ Reference: std::ref
- 《C++ Concurrency in Action》 by Anthony Williams - 深入了解C++多线程编程的优秀资源