2.线程函数中的数据未定义错误

\1. 传递临时变量的问题:

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <thread>
void foo(int& x) {
x += 1;
}
int main() {
std::thread t(foo, 1); // 传递临时变量
t.join();
return 0;
}

在这个例子中,我们定义了一个名为foo的函数,它接受一个整数引用作为参数,并将该引用加1。然后,我们创建了一个名为t的线程,将foo函数以及一个临时变量1作为参数传递给它。这样会导致在线程函数执行时,临时变量1被销毁,从而导致未定义行为。

解决方案是将变量复制到一个持久的对象中,然后将该对象传递给线程。例如,我们可以将1复制到一个int类型的变量中,然后将该变量的引用传递给线程。

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <thread>
void foo(int& x) {
x += 1;
}
int main() {
int x = 1; // 将变量复制到一个持久的对象中
std::thread t(foo, std::ref(x)); // 将变量的引用传递给线程
t.join();
return 0;
}

\2. 传递指针或引用指向局部变量的问题:

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <thread>
void foo(int* ptr) {
std::cout << *ptr << std::endl; // 访问已经被销毁的指针
}
int main() {
int x = 1;
std::thread t(foo, &x); // 传递指向局部变量的指针
t.join();
return 0;
}

在这个例子中,我们定义了一个名为foo的函数,它接受一个整型指针作为参数,并输出该指针所指向的整数值。然后,我们创建了一个名为t的线程,将foo函数以及指向局部变量x的指针作为参数传递给它。这样会导致在线程函数执行时,指向局部变量x的指针已经被销毁,从而导致未定义行为。

解决方案是将指针或引用指向堆上的变量,或使用std::shared_ptr等智能指针来管理对象的生命周期。例如,我们可以使用new运算符在堆上分配一个整数变量,并将指针指向该变量。

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <thread>
void foo(int* ptr) {
std::cout << *ptr << std::endl;
delete ptr; // 在使用完指针后,需要手动释放内存
}
int main() {
int* ptr = new int(1); // 在堆上分配一个整数变量
std::thread t(foo, ptr); // 将指针传递给线程
t.join();
return 0;
}

\3. 传递指针或引用指向已释放的内存的问题:

1
2
3
4
5
6
7
8
9
10
11
12
#include <iostream>
#include <thread>
void foo(int& x) {
std::cout << x << std::endl; // 访问已经被释放的内存
}
int main() {
int* ptr = new int(1);
std::thread t(foo, *ptr); // 传递已经释放的内存
delete ptr;
t.join();
return 0;
}

在这个例子中,我们定义了一个名为foo的函数,它接受一个整数引用作为参数,并输出该引用的值。然后,我们创建了一个名为t的线程,将foo函数以及一个已经被释放的指针所指向的整数值作为参数传递给它解决方案是确保在线程函数执行期间,被传递的对象的生命周期是有效的。例如,在主线程中创建并初始化对象,然后将对象的引用传递给线程。

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>
#include <thread>
void foo(int& x) {
std::cout << x << std::endl;
}
int main() {
int x = 1;
std::thread t(foo, std::ref(x)); // 将变量的引用传递给线程
t.join();
return 0;
}

在这个例子中,我们创建了一个名为x的整数变量,并初始化为1。然后,我们创建了一个名为t的线程,将foo函数以及变量x的引用作为参数传递给它。这样可以确保在线程函数执行期间,变量x的生命周期是有效的。

\4. 类成员函数作为入口函数,类对象被提前释放

错误示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <iostream>
#include <thread>

class MyClass {
public:
void func() {
std::cout << "Thread " << std::this_thread::get_id()
<< " started" << std::endl;
// do some work
std::cout << "Thread " << std::this_thread::get_id()
<< " finished" << std::endl;
}
};

int main() {
MyClass obj;
std::thread t(&MyClass::func, &obj);
// obj 被提前销毁了,会导致未定义的行为
return 0;
}

上面的代码中,在创建线程之后,obj 对象立即被销毁了,这会导致在线程执行时无法访问 obj 对象,可能会导致程序崩溃或者产生未定义的行为。

为了避免这个问题,可以使用 std::shared_ptr 来管理类对象的生命周期,确保在线程执行期间对象不会被销毁。具体来说,可以在创建线程之前,将类对象的指针封装在一个 std::shared_ptr 对象中,并将其作为参数传递给线程。这样,在线程执行期间,即使类对象的所有者释放了其所有权,std::shared_ptr 仍然会保持对象的生命周期,直到线程结束。

以下是使用 std::shared_ptr 修复上面错误的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include <iostream>
#include <thread>
#include <memory>

class MyClass {
public:
void func() {
std::cout << "Thread " << std::this_thread::get_id()
<< " started" << std::endl;
// do some work
std::cout << "Thread " << std::this_thread::get_id()
<< " finished" << std::endl;
}
};

int main() {
std::shared_ptr<MyClass> obj = std::make_shared<MyClass>();
std::thread t(&MyClass::func, obj);
t.join();
return 0;
}

上面的代码中,使用 std::make_shared 创建了一个 MyClass 类对象,并将其封装在一个 std::shared_ptr 对象中。然后,将 std::shared_ptr 对象作为参数传递给线程。这样,在线程执行期间,即使 obj 对象的所有者释放了其所有权,std::shared_ptr 仍然会保持对象的生命周期,直到线程结束。

5.入口函数为类的私有成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <thread>

class MyClass {
private:
friend void myThreadFunc(MyClass* obj);
void privateFunc(){
std::cout << "Thread "
<< std::this_thread::get_id() << " privateFunc" << std::endl;
}
};

void myThreadFunc(MyClass* obj) {
obj->privateFunc();
}

int main() {
MyClass obj;
std::thread thread_1(myThreadFunc, &obj);
thread_1.join();
return 0;
}

上面的代码中,将 myThreadFunc 定义为 MyClass 类的友元函数,并在函数中调用 privateFunc 函数。在创建线程时,需要将类对象的指针作为参数传递给线程。


2.线程函数中的数据未定义错误
http://binbo-zappy.github.io/2024/11/27/cpp-多线程/2-线程函数中的数据未定义错误/
作者
Binbo
发布于
2024年11月27日
许可协议