【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下要避免隱式轉換,因為此時子線程可能還來不及轉換主線程就結束了,應該在構造線程時,用參數構造一個臨時對象傳入。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM