C + + concurrency and multithreading

1. Pass temporary object as thread parameter

1.1 pitfalls to avoid (explanation 1)

First analyze the parameter i. The analysis shows that for i, the function in the thread originally uses reference transfer, but actually uses value transfer. Therefore, even if detach is used in the main thread to end the main thread first, the sub thread is still safe.

#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;


void myprint(const int &i, char *pmybuf)
{
	//The analysis shows that i is not a reference to mavr, but actually a value transfer
	//Even if the main thread is detach, the child thread is still safe
	cout << i << endl;
	cout << pmybuf << endl;
}

int main()
{

	//1:  Passing a temporary object as a thread parameter
	//1.1 pitfalls to avoid (explanation 1)
	int mvar = 1;
	int& mvary = mvar;
	char mybuf[] = "this is a test!";
	thread mytobj(myprint, mvar, mybuf);
	//mytobj.join();
	mytobj.detach(); //The sub thread and the main thread execute respectively

	cout << "I LOVE CHINA" << endl;
	return 0;
}

Use shift+F9 to refer to the corresponding parameters



Let's look at the parameter pmybuf. For the original character array, the address is affc38
(set two breakpoints and press F5 to quickly move to the next breakpoint)

The address of the array in the passed in function is:

The two addresses are the same. Reference passing is used here, which makes the child thread unsafe. Therefore, when using detach, it is best not to use references and pointers.

1.2 pitfalls to avoid 2

When we change the second parameter in the sub thread execution function, namely char * pmybuf, to const string & pmybuf, we will check the addresses before and after entering the thread. The address before entering the sub thread is different from that after entering the sub thread, indicating that it is value passing, but is such a sub thread really safe?
In fact, the system only uses mybuf to convert string after mybuf has been recycled (the main function has been executed)

#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;


void myprint(const int &i, const string &pmybuf)
{
	//The analysis shows that i is not a reference to mavr, but actually a value transfer
	//Even if the main thread is detach, the child thread is still safe
	cout << i << endl;
	cout << pmybuf << endl;  //There is definitely a problem when the pointer is detach pointer
}

int main()
{

	//1:  Passing a temporary object as a thread parameter
	//1.1 pitfalls to avoid (explanation 1)
	//1.2 pitfalls to avoid (explanation 2)
	int mvar = 1;
	int& mvary = mvar;
	char mybuf[] = "this is a test!";
	thread mytobj(myprint, mvar, mybuf);         //When did mybuf convert to string
	//In fact, mybuf is recycled (the main function is executed), and the system uses mybuf to convert string
	//mytobj.join();
	mytobj.detach(); //The sub thread and the main thread execute respectively

	cout << "I LOVE CHINA" << endl;
	return 0;
}



How should it be stable?
First, let's modify the code:
Create class A, write out the parametric structure, copy structure and destructor respectively, then pass in the required parameters in the thread function, and use the join() function. The results are as follows:

#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;


class A
{
public:
	int m_i;
	//The type conversion constructor converts an int integer to a class object
	A(int a) :m_i(a)         //Initialization variable m_i is equivalent to realizing m in the function_ i = a
	{
		cout << "[A::A(int a)Constructor Execution ]" << endl;
	}
	A(const A &a) :m_i(a.m_i) { cout << "[A::A(const A)Copy constructor execution]" << endl; }
	~A()
	{
		cout << "[A::~A(Destructor execution]" << endl;
	}

};

//void myprint(const int &i, const string &pmybuf)
//{
//	//The analysis shows that i is not a reference to mavr, but actually a value transfer
//	//Even if the main thread is detach, the child thread is still safe
//	cout << i << endl;
//	cout << pmybuf << endl;  // There is definitely a problem when the pointer is detach pointer
//}


void myprint(const int i, const A &pmybuf)
{
	
	cout << &pmybuf << endl;			//Prints the address of the pmybuf object
	return;
}

int main()
{

	//1:  Passing a temporary object as a thread parameter
	//1.1 pitfalls to avoid (explanation 1)
	//1.2 pitfalls to avoid (explanation 2)
	//int mvar = 1;
	//int& mvary = mvar;
	//char mybuf[] = "this is a test!";

	//thread mytobj(myprint, mvar, mybuf);         // When did mybuf convert to string
	//In fact, mybuf is recycled (the main function is executed), and the system uses mybuf to convert string

	//Here, we directly convert mybuf into a string object, which is an object that can be used in the thread  
	//Use the custom class A to verify
	//thread mytobj(myprint, mvar, string(mybuf));

	int mvar = 1;
	int mysecondpar = 12;
	thread mytobj(myprint, mvar, mysecondpar);     //This is the second parameter you want to pass to the myprint function when you want to convert the shape into A class A object

	mytobj.join();
	

	cout << "I LOVE CHINA" << endl; 
	return 0;
}



Execute the constructor first, then the destructor, and successfully convert the int type mysecondpar into A class A object through the parameterized constructor.

After changing the join function to the detach function, comment out the cout in the main function to make the main function end quickly. The following results can be observed:

If nothing is printed, it means that the main thread ends before the thread executes the function to create the class object through the parametric construction, and the sub thread will have problems.
The solution to this problem is to create temporary objects

#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;


class A
{
public:
	int m_i;
	//The type conversion constructor converts an int integer to a class object
	A(int a) :m_i(a)         //Initialization variable m_i is equivalent to realizing m in the function_ i = a
	{
		cout << "[A::A(int a)Constructor Execution ]" << endl;
	}
	A(const A &a) :m_i(a.m_i) { cout << "[A::A(const A)Copy constructor execution]" << endl; }
	~A()
	{
		cout << "[A::~A(Destructor execution]" << endl;
	}

};

//void myprint(const int &i, const string &pmybuf)
//{
//	//The analysis shows that i is not a reference to mavr, but actually a value transfer
//	//Even if the main thread is detach, the child thread is still safe
//	cout << i << endl;
//	cout << pmybuf << endl;  // There is definitely a problem when the pointer is detach pointer
//}


void myprint(const int i, const A &pmybuf)
{
	
	cout << &pmybuf << endl;			//Prints the address of the pmybuf object
	return;
}

int main()
{

	//1:  Passing a temporary object as a thread parameter
	//1.1 pitfalls to avoid (explanation 1)
	//1.2 pitfalls to avoid (explanation 2)
	//int mvar = 1;
	//int& mvary = mvar;
	//char mybuf[] = "this is a test!";

	//thread mytobj(myprint, mvar, mybuf);         // When did mybuf convert to string
	//In fact, mybuf is recycled (the main function is executed), and the system uses mybuf to convert string

	//Here, we directly convert mybuf into a string object, which is an object that can be used in the thread  
	//Use the custom class A to verify
	//thread mytobj(myprint, mvar, string(mybuf));

	int mvar = 1;
	int mysecondpar = 12;

	//thread mytobj(myprint, mvar, mysecondpar);     // This is the second parameter you want to pass to the myprint function when you want to convert the shape into A class A object

	thread mytobj(myprint, mvar, A(mysecondpar));     //Solve the above problem by creating temporary objects
	//mytobj.join();
	mytobj.detach();

	//cout << "I LOVE CHINA" << endl; 
	return 0;
}

Operation results

This shows that temporary objects are created in the main thread, that is, it is feasible to pass parameters by constructing temporary objects while creating threads.
Fact 1: as long as the temporarily constructed class A object is passed to the sub thread as A parameter, the second parameter component of the sub thread can be generated before the end of the main thread, so as to ensure that even if the main thread ends after the detach, the sub thread will not use invalid parameters

1.3 summary

1. If a simple type parameter such as int is passed, it is recommended to pass it directly with value instead of reference.

2. If class objects are passed, avoid implicit type conversion. You should construct a temporary object in the line of creating thread, and then connect it with a reference in the function parameter, otherwise the system will construct it again.

3. It is recommended not to use detach but only join, so that there is no illegal reference to memory caused by local variable failure.

2. Continue with the temporary object as a thread parameter

2.1 concept of thread id

**Each thread actually corresponds to a number, and the corresponding number of each thread is different.
The thread ID can be obtained by using the standard function std::this_thread::get_id()

2.2 capture of temporary objects

To verify that implicit type conversion is performed in the child thread, we can use the method of obtaining thread id, and the code is as follows:**

#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;


class A
{
public:
	int m_i;
	//The type conversion constructor converts an int integer to a class object
	A(int a) :m_i(a)         //Initialization variable m_i is equivalent to realizing m in the function_ i = a
	{
		cout << "[A::A(int a)Constructor Execution ]" <<this<<"this_threadid"<<this_thread::get_id()<< endl;
	}
	A(const A &a) :m_i(a.m_i) { cout << "[A::A(const A)Copy constructor execution]" <<this<< "this_threadid" << this_thread::get_id() << endl; }
	~A()
	{
		cout << "[A::~A(Destructor execution]" <<this<< "this_threadid" << this_thread::get_id() << endl;
	}

};

//void myprint(const int &i, const string &pmybuf)
//{
//	//The analysis shows that i is not a reference to mavr, but actually a value transfer
//	//Even if the main thread is detach, the child thread is still safe
//	cout << i << endl;
//	cout << pmybuf << endl;  // There is definitely a problem when the pointer is detach pointer
//}


//void myprint(const int i, const A &pmybuf)
//{
//	
//	cout << &pmybuf << endl; 			// Prints the address of the pmybuf object
//	return;
//}

void myprint2(const A &pmybuf)
{

	cout <<"The sub thread parameter address is:"<< &pmybuf << endl;			//Prints the address of the pmybuf object
	cout << "threadid = " << this_thread::get_id() << endl;
	return;
}

int main()
{

	//1:  Passing a temporary object as a thread parameter
	//1.1 pitfalls to avoid (explanation 1)
	//1.2 pitfalls to avoid (explanation 2)
	//int mvar = 1;
	//int& mvary = mvar;
	//char mybuf[] = "this is a test!";

	//thread mytobj(myprint, mvar, mybuf);         // When did mybuf convert to string
	//In fact, mybuf is recycled (the main function is executed), and the system uses mybuf to convert string

	//Here, we directly convert mybuf into a string object, which is an object that can be used in the thread  
	//Use the custom class A to verify
	//thread mytobj(myprint, mvar, string(mybuf));

	//int mvar = 1;
	//int mysecondpar = 12;

	thread mytobj(myprint, mvar, mysecondpar);     //This is the second parameter you want to pass to the myprint function when you want to convert the shape into A class A object

	//thread mytobj(myprint, mvar, A(mysecondpar));     // Solve the above problem by creating temporary objects
	//mytobj.join();


	cout << "Main thread id For:" << this_thread::get_id() << endl;
	int mvar = 1;
	std::thread mytobj(myprint2, mvar);
	mytobj.join();

	//cout << "I LOVE CHINA" << endl; 
	return 0;
}

The result is shown in the figure below. It can be seen that the process of converting int to class A is carried out in the sub thread.

If it is changed to std::thread mytobj(myprint2, A(mvar)); Observation results

After using the temporary object, it can be used safely.
Even if detach is used, it can be used safely

3. Pass class objects and smart pointers as thread parameters

#include "pch.h"
#include <iostream>
#include <thread>
#include <string>
#include <vector>
#include <map>
using namespace std;


class A
{
public:
	mutable int m_i;             //mutable keyword can modify const constant
	//The type conversion constructor converts an int integer to a class object
	A(int a) :m_i(a)         //Initialization variable m_i is equivalent to realizing m in the function_ i = a
	{
		cout << "[A::A(int a)Constructor Execution ]" <<this<<"this_threadid"<<this_thread::get_id()<< endl;
	}
	A(const A &a) :m_i(a.m_i) { cout << "[A::A(const A)Copy constructor execution]" <<this<< "this_threadid" << this_thread::get_id() << endl; }
	~A()
	{
		cout << "[A::~A(Destructor execution]" <<this<< "this_threadid" << this_thread::get_id() << endl;
	}

};

//void myprint(const int &i, const string &pmybuf)
//{
//	//The analysis shows that i is not a reference to mavr, but actually a value transfer
//	//Even if the main thread is detach, the child thread is still safe
//	cout << i << endl;
//	cout << pmybuf << endl;  // There is definitely a problem when the pointer is detach pointer
//}


//void myprint(const int i, const A &pmybuf)
//{
//	
//	cout << &pmybuf << endl; 			// Prints the address of the pmybuf object
//	return;
//}

void myprint2(const A &pmybuf)
{
	pmybuf.m_i = 199;            

	cout <<"The sub thread parameter address is:"<< &pmybuf << endl;			//Prints the address of the pmybuf object
	cout << "threadid = " << this_thread::get_id() << endl;
	return;
}

int main()
{

	//1:  Passing a temporary object as a thread parameter
	//1.1 pitfalls to avoid (explanation 1)
	//1.2 pitfalls to avoid (explanation 2)
	//int mvar = 1;
	//int& mvary = mvar;
	//char mybuf[] = "this is a test!";

	//thread mytobj(myprint, mvar, mybuf);         // When did mybuf convert to string
	//In fact, mybuf is recycled (the main function is executed), and the system uses mybuf to convert string

	//Here, we directly convert mybuf into a string object, which is an object that can be used in the thread  
	//Use the custom class A to verify
	//thread mytobj(myprint, mvar, string(mybuf));

	//int mvar = 1;
	//int mysecondpar = 12;

	thread mytobj(myprint, mvar, mysecondpar);     //This is the second parameter you want to pass to the myprint function when you want to convert the shape into A class A object

	//thread mytobj(myprint, mvar, A(mysecondpar));     // Solve the above problem by creating temporary objects
	//mytobj.join();


	/*cout << "The main thread ID is: "< this_thread:: get_id() < < endl;
	int mvar = 1;
	std::thread mytobj(myprint2, A(mvar));
	mytobj.join();*/


	A myobj(10);                  //Generate A class A object 
	thread mytobj(myprint2, myobj);          //If the thread has called the main thread, it will not affect the end of the myobj class
	mytobj.join();


	//cout << "I LOVE CHINA" << endl; 
	return 0;
}




Keywords: C++ Multithreading pointer

Added by thinguy on Fri, 18 Feb 2022 02:21:01 +0200