跳到主要内容

C++ 线程参数

介绍

在C++多线程编程中,我们经常需要向线程传递参数,使线程能够处理特定的数据或执行自定义的操作。C++11引入的std::thread类提供了强大而灵活的方式来创建线程并传递各种类型的参数。本文将详细介绍如何在C++中向线程函数传递参数,包括基本数据类型、引用、指针和类对象等不同类型的参数。

基本参数传递

传递简单参数

最基本的情况是向线程函数传递简单的基本类型参数:

cpp
#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::refstd::cref

cpp
#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
警告

传递引用给线程时要确保被引用的对象在线程执行期间一直有效。如果对象生命周期结束但线程仍在执行,会导致未定义行为。

传递指针参数

向线程传递指针类似于传递基本类型,但需要注意指针指向的数据的生命周期:

cpp
#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
注意

传递指针时,必须确保指针指向的内存在线程执行期间依然有效。如果指针指向的是局部变量,且线程在函数返回后仍在执行,就会导致严重的内存问题。

传递类成员函数和对象

调用类的成员函数作为线程函数

要在线程中调用类的成员函数,需要传递对象实例和成员函数指针:

cpp
#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的移动语义,可以将临时对象高效地传递给线程:

cpp
#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表达式与线程配合使用非常灵活:

cpp
#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])则允许修改原始变量。

实际应用案例

并行数据处理

下面是一个使用多线程对向量进行并行求和的实际案例:

cpp
#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

在这个实际案例中,我们将一个大向量分成几个区块,每个线程处理一个区块,最后合并结果。这展示了如何有效地传递引用、范围和数据给不同线程,以实现数据的并行处理。

线程参数传递的注意事项

在使用线程参数时,需要注意以下几点:

  1. 数据竞争:当多个线程同时访问同一个变量并且至少有一个线程执行写操作时,可能会发生数据竞争,导致未定义行为。使用互斥量或原子操作来避免这种情况。

  2. 生命周期:确保传递给线程的引用和指针在线程执行期间保持有效。

  3. 参数复制:默认情况下,参数会被复制到线程的存储空间,对于大型对象可能会影响性能。

  4. 传递引用:使用std::refstd::cref来传递引用,否则会发生复制。

  5. 异常处理:在线程函数中捕获异常,因为未捕获的异常会导致程序终止。

总结

在C++多线程编程中,正确传递参数对于编写健壮的多线程程序至关重要。本文介绍了:

  • 基本类型参数的传递
  • 使用std::refstd::cref传递引用参数
  • 指针参数的传递和注意事项
  • 类成员函数作为线程函数
  • 移动语义和临时对象的传递
  • Lambda表达式与线程的结合使用
  • 实际应用案例展示如何利用线程参数进行并行计算

掌握这些技术可以让你更有效地利用C++的多线程能力,开发出高效、可靠的并发程序。

练习

  1. 创建一个程序,使用多线程计算斐波那契数列的不同部分,然后合并结果。
  2. 编写一个程序,在一个线程中修改一个字符串,在另一个线程中读取该字符串,并使用适当的同步机制确保安全访问。
  3. 实现一个并行的图像处理程序,每个线程处理图像的一部分(例如,应用滤镜或调整亮度)。

额外资源