【C++多线程】传递参数

  线程可以共享进程的内存空间,线程拥有自己独立内存。

  关于参数的传递,std::thread的构造函数只会单纯的复制传入的变量,特别需要注意的是传递引用时,传入的是值的副本,也就是说子线程中的修改影响不了主线程中的值。

值传递

  主线程中的值,被拷贝一份传到了子线程中。

 1 #include <iostream>
 2 #include <thread>
 3 
 4 using namespace std;
 5 
 6 void test(int ti, int tj)
 7 {
 8     cout << "子线程开始" << endl;
 9     //ti的内存地址0x0055f69c {4},tj的内存地址0x0055f6a0 {5}
10     cout << ti << " " << tj << endl;  
11     cout << "子线程结束" << endl;
12     return;
13 }
14 
15 
16 int main()
17 {
18     cout << "主线程开始" << endl;
19     //i的内存地址0x001efdfc {4},j的内存地址0x001efdf0 {5}
20     int i = 4, j = 5;  
21     thread t(test, i, j);
22     t.join();
23     cout << "主线程结束!" << endl;
24     return 0;
25 }

传引用

  从下面的运行结果,可以看出,即使是用引用来接收传的值,也是会将其拷贝一份到子线程的独立内存中,这一点与我们编写普通程序时不同。这是因为线程的创建属于函数式编程,所以为了传引用C++中才引入了std::ref()。关于std::ref()

 1 #include <iostream>
 2 #include <thread>
 3 
 4 using namespace std;
 5 
 6 class A{
 7 public:
 8      int ai;
 9      A (int i): ai(i) { }
10 };
11 
12 //这种情况必须在引用前加const,否则会出错。目前本人的觉得可能是因为临时对象具有常性
13 void test(const int &ti, const A &t) 
14 {
15     cout << "子线程开始" << endl;
16     //ti的内存地址0x0126d2ec {4},t.ai的内存地址0x0126d2e8 {ai=5 }
17     cout << ti << " " << t.ai << endl;  
18     cout << "子线程结束" << endl;
19     return;
20 }
21 
22 
23 int main()
24 {
25     cout << "主线程开始" << endl;
26     //i的内存地址0x010ff834 {4},a的内存地址0x010ff828 {ai=5 }
27     int i = 4;  
28     A a = A(5);
29     thread t(test, i, a);
30     t.join();
31     cout << "主线程结束!" << endl;
32     return 0;
33 }

   那么如果我们真的需要像一般程序那样传递引用呢,即在子线程中的修改能够反映到主线程中。此时需要使用std::ref()但是注意如果我们会在子线中改变它,此时用于接收ref()的那个参数前不能加const。关于C++多线程中的参数传引用问题,我目前只是记住了这个现象,关于原理还需后期研究源码继续学习。

 1 #include <iostream>
 2 #include <thread>
 3 
 4 using namespace std;
 5 
 6 class A {
 7 public:
 8     int ai;
 9     A(int i) : ai(i) { }
10 };
11 
12 //接收ref()的那个参数前不能加const,因为我们会改变那个值
13 void test(int& ti, const A& t)
14 {
15     cout << "子线程开始" << endl;
16     cout << ti << " " << t.ai << endl;
17     ti++;
18     cout << "子线程结束" << endl;
19     return;
20 }
21 
22 
23 int main()
24 {
25     cout << "主线程开始" << endl;
26     int i = 4;
27     A a = A(5);
28     thread t(test, ref(i), a);
29     t.join();
30     cout << "i改变:" << i << endl;
31     cout << "主线程结束!" << endl;
32     return 0;
33 }

   传入类对象时,使用引用来接收比用值接收更高效。

 1 #include <iostream>
 2 #include <thread>
 3 
 4 using namespace std;
 5 
 6 class A {
 7 public:
 8     int ai;
 9     A (int i) : ai(i) 
10     { 
11         cout << "构造" << this << endl; 
12     }
13 
14     A (const A& a) :ai(a.ai) {
15         cout << "拷贝构造" << this << endl;
16     }
17 
18     ~A()
19     {
20         cout << "析构" << this << endl;
21     }
22 };
23 
24 //void test(const A a)
25 void test(const A& a)
26 {
27     cout << "子线程开始" << endl;
28     cout << "子线程结束" << endl;
29     return;
30 }
31 
32 
33 int main()
34 {
35     cout << "主线程开始" << endl;
36     int i = 4;
37     thread t(test, A(i));
38     t.join();
39     cout << "主线程结束!" << endl;
40     return 0;
41 }

 

 

 

传指针

  从下面的运行结果,可以看出,主线程和子线程中的指针都是指向同一块内存。所以在这种情况下会有一个陷阱,如果使用detach(),则当主线程崩溃或者正常结束后,该块内存被回收,若此时子线程没有结束,那么子线程中指针的访问将未定义,程序会出错。

 1 #include <iostream>
 2 #include <thread>
 3 
 4 using namespace std;
 5 
 6 
 7 void test(char *p) 
 8 {
 9     cout << "子线程开始" << endl;
10     //0x004ffeb4 "hello"
11     cout << p << endl;  
12     cout << "子线程结束" << endl;
13     return;
14 }
15 
16 
17 int main()
18 {
19     cout << "主线程开始" << endl;
20     //0x004ffeb4 "hello"
21     char s[] = "hello";
22     thread t(test, s);
23     t.join();
24     cout << "主线程结束!" << endl;
25     return 0;
26 }

 

传临时对象

   用临时变量作为实参时,会更高效,由于临时变量会隐式自动进行移动操作,这就减少了整体构造函数的调用次数。而一个命名变量的移动操作就需要std::move()。

 1 #include <iostream>
 2 #include <thread>
 3 
 4 using namespace std;
 5 
 6 class A {
 7 public:
 8     int ai;
 9     A (int i) : ai(i) 
10     { 
11         cout << "构造" << this << endl; 
12     }
13 
14     A (const A& a) :ai(a.ai) {
15         cout << "拷贝构造" << this << endl;
16     }
17 
18     ~A()
19     {
20         cout << "析构" << this << endl;
21     }
22 };
23 
24 void test(const A& a)
25 {
26     cout << "子线程开始" << endl;
27     cout << "子线程结束" << endl;
28     return;
29 }
30 
31 
32 int main()
33 {
34     cout << "主线程开始" << endl;
35     int i = 4;
36     thread t(test, A(i));
37     t.join();
38     cout << "主线程结束!" << endl;
39     return 0;
40 }

 

总结

  1、使用引用和指针是要注意;

  2、对于内置简单类型,建议传值;

  3、对于类对象,建议使用引用来接收,以为使用引用会只会构造两次,而传值会构造三次;

  4、在detach下要避免隐式转换,因为此时子线程可能还来不及转换主线程就结束了,应该在构造线程时,用参数构造一个临时对象传入。

posted @ 2020-06-07 10:54  Chen沉尘  阅读(11536)  评论(3编辑  收藏  举报