Detailed explanation of C + + multithreading transmission

catalogue

1. Process of thread parameter transfer

1.1 arguments of built-in types

1.1.1 parameter transfer by value

1.1.2 if you want to pass by reference, you need to call std::ref

1.2 class type arguments

1.2.1 passing is an lvalue object

1.2.2 passing is a temporary object (i.e. right value object)

1.2.3 parameters passed need implicit type conversion

1.2.4 the parameters passed are pointers

1.3 pass in smart pointer unique_ptr

1. Process of thread parameter transfer

The following is the source code of thread

template< class Function, class... Args >
explicit thread( Function&& f, Args&&... args );

The source code is very complex. I can't understand it anyway. However, one thing is certain. By default, arguments are passed in by value to generate a copy into the thread (many people may have seen this sentence, but they may not know the specific details, as illustrated below)

The argument is passed from the main thread to the thread function of the child thread, which needs to be passed twice. The first time occurs when std::thread is constructed, the argument is passed by value and saved in the tuple of thread in the form of copy. This process occurs in the main thread. The second time occurs when passing to the thread function. This transfer is initiated by the sub thread, that is, it occurs in the sub thread, and the copy previously saved in std::thread is passed into the thread function in the form of right value (by calling std::move()).

1.1 arguments of built-in types

1.1.1 parameter transfer by value

By default, all parameters (including the callable object of the first parameter) are saved in tuple in std::thread object by value and in the form of copy.

The implementation of this is similar to std::bind (those who don't know bind can learn it)

void func(int& a) //lvalue reference 
{
	a = 6;
}

int main()
{
	int b = 1;
	thread t1(func,b); //Wrong. Copy the argument b by value to generate a copy, and store the copy in the tuple of thread,
                       //Then call std::move on the copy to generate an right value, while the parameter a in func is an left value
                       //Reference, cannot be bound to right value
	cout << b << endl;
	t1.join();
	return 0;
}

1.1.2 if you want to pass by reference, you need to call std::ref

void func(int& a) //lvalue reference 
{
	a = 6;
}

int main()
{
	int b = 1;
	thread t1(func,std::ref(b); //When std::ref passes parameters, a temporary object of std::ref type will be created first,
                           //It holds a reference to b. Then the std::ref is saved in the form of a copy
                         //Thread in tuple. This copy is then move d to the thread function because std::ref is overloaded
                        //Operator T & (), so it will be implicitly converted to int & type, so it will act as if b is direct
                          //Is passed by reference to the thread function

	cout << b << endl;//The output of b is 6
	t1.join();
	return 0;
}

1.2 class type arguments

1.2.1 passing is an lvalue object

class A {
private:
	int m_i;
public:
	A(int i) :m_i(i) { cout << "Transformation structure" <<std::this_thread::get_id()<<endl; }
	A(const A& a):m_i(a.m_i) {cout << "copy construction " <<std::this_thread::get_id()<< endl;}
 	A(A&& a):m_i(a.m_i) { cout << "Mobile structure" << std::this_thread::get_id()<<endl;}
	~A() {cout << "Destructor" <<std::this_thread::get_id()<< endl;}
};

void myPrint2(const A& a) 
{cout << "The child thread parameter address is" <<&a<<std::this_thread::get_id()<< endl;}//4. The sub thread parameter address is 0157D48049564

int main() {
	int i = 5;
    A myobj(i);//1. Conversion structure 25964 6 Destructor 25964
    cout << "Main thread id yes" <<std::this_thread::get_id()<< endl;//2. The main thread id is 25964
 
   thread mytobj(myPrint2,myobj); //3. Copy structure 25964 5 Destructor 49564
                                 //Analyze why the copy structure is called above
                                 //myobj is an lvalue object, so the copy construct is called to generate
                                 //Put a copy into the tuple. This process occurs in the main thread
	mytobj.join();

	return 0;
}

1.2.2 passing is a temporary object (i.e. right value object)

class A {
...//The definition is the same as before
};

void myPrint2(const A& a) //The definition is the same as before
{...}    //4. The sub thread parameter address is 00DED638 30492

int main() {
	int i = 5;
    cout << "Main thread id yes" <<std::this_thread::get_id()<< endl;//1. The main thread id is 33312
thread mytobj(myPrint2,A(i));//2. Conversion structure 33312, 3 Mobile structure 33312 
                              //4. Destructor 33312 5 Destructor 30492
                               //First, A (i) will call the transformation construct to generate A temporary object
                                //Then copy the temporary object into thread by value
                                // Since the temporary object is an R-value, the move construct is called
                                //Both constructs occur in the main thread
	mytobj.join();

	return 0;
}

There is another possibility about temporary objects

class A {
...//The definition is the same as before
};

void myPrint2(const A& a) //The definition is the same as before
{...}   //4. The sub thread parameter address is 00E7D800 28216
 
int main() {
	int i = 5;
   A a(i); //1. Conversion structure 41312 6 Destructor 41312
    cout << "Main thread id yes" <<std::this_thread::get_id()<< endl;//2. The main thread id is 41312
thread mytobj(myPrint2,std::move(a));//3. Mobile structure 41312 5 Destructor 28216
                              //4. Destructor 33312 5 Destructor 30492
                               //Because move(a) returns an R-value, it will call the move construct to generate the R-value to the thread 
                                 //tuple. Similarly, this step occurs in the main thread
	mytobj.join();

	return 0;
}

1.2.3 parameters passed need implicit type conversion

class A {
...//The definition is the same as before
};

void myPrint2(const A& a) //The definition is the same as before
{...}    //3. The sub thread parameter address is 00FFF7E4 28552

int main() {
	int i = 5;
    cout << "Main thread id yes" <<std::this_thread::get_id()<< endl;//1. The main thread id is 50076
thread mytobj(myPrint2,i);//2. Conversion structure 28552 4 Destructor 28552
                          //Analysis: first, i passes the copy to the thread by value, and its type is still int. this step occurs in the main thread
                          //Then, when the sub thread calls move to pass parameters to the thread function, the implicit type conversion (call) from int to A occurs 
                            /Transformation construct), which occurs in the child thread
	mytobj.join();

	return 0;
}

1.2.4 the parameters passed are pointers

  void func(const string& s)
 { cout <<"Child thread id yes " << std::this_thread::get_id() << endl; }

int main(){

  const char* name = "Santa Claus";
  
     thread t(func, &w, name); //ok.  First, name is saved as a copy of const char * in the main thread
                              //In thread, when parameters are passed to the thread function func, the previous copy of name will be implicitly transferred first 
                               //Replace with a string temporary object, and then call the parameter s passed to func by move
                             //At the same time, note that this implicit conversion occurs when the sub thread is called, that is, the temporary is created in the sub thread 
                             // Time object. This needs to ensure that the life cycle of the main thread is longer than that of the child thread, otherwise the name copy will be deleted 
                             /Becomes a wild pointer, so it cannot be constructed correctly string Object.
    
  

    //std::thread t6(&Widget::func, &w, string(name)); // In order to avoid the bug s caused by the above implicit conversion. can 
                                                      //To construct a string temporary object in the main thread first, 
                                                      //Then pass it into thread. So even if it calls 
                                                     //detach, child threads are also safe

    t.join();  //If it is changed to t.detach here, and if the life of the main thread is at the end of this line (it means that the main thread is in front of the child thread) 
               //When the operation is completed, the wild pointer phenomenon may occur.
}

1.3 pass in smart pointer unique_ptr

Smart pointer is actually a template class. Let's talk about it separately here

void myPrint3(unique_ptr<A> pgn) {cout << myp.get() << endl;}//00E6BEB8

int main() {

 unique_ptr<int> myp(new int(100));
 
   thread mytobj(myPrint3,myp); //Error, first unique_prt cannot copy and can only move. And myp is a
                               //Lvalue, which cannot be moved. Construct a copy and put it into the thread
   thread mytobj(myPrint3,std::move(myp));//ok,std::move(myp) returns an R-value, so it calls the move constructor 
                                          //Generate a copy and put it into the thread. All these happen in the main thread
	mytobj.join();

	return 0;
}

Keywords: C++ Multithreading

Added by Ironphp on Sun, 20 Feb 2022 13:49:17 +0200