Systematically learn about C + + standard multithreading - thread management

Posted by Negligence on Fri, 28 Jan 2022 13:04:38 +0100

catalogue

catalogue

Basic thread management

Start thread

Wait for the thread to complete

Running threads in the background

Pass parameters to thread function

Pass a parameter

Value transmission

Pass reference

Pass pointer

Pass multiple parameters

Transfer ownership of threads

Select the number of threads at run time

result

Identify thread

Basic thread management

Start thread

To start a thread, I have summarized three methods. The first is the simplest way to use anonymous functions, the second is to use curly braces, and the third is to use curly braces.

The thread has been started when it is created. There is no need to display the start. This is different from python. I remember that the thread of Python can only be started when it is displayed.

Look at the code:

#include <iostream>
#include <thread>

using namespace std;

void func2()
{
	cout << " -- thread-2 cout --" << endl;
}
void func3()
{
	cout << " -- thread-3 cout --" << endl;
}
int main()
{
	std::thread t1([]() {
		cout << " -- thread-1 cout --" << endl;	
	});
	std::thread t2(func2);
	std::thread t3{func3};
	cout << "-- main cout  --" << endl;
	if (t1.joinable())
	{
		t1.join();
	}
	
	t2.join();
	t3.join();
	
	return 0;
}

result:

Wait for the thread to complete

Waiting for the thread to complete is to call the jion function.

	t1.join();
	t2.join();
	t3.join();

Without these three functions, the code may collapse.

Because it is possible that the new thread has not finished running, but the main function has finished running. When the thread function has finished running, you need to return to the main function. It is found that the main function has finished running and there is no main function in memory, so it crashes. With the join function, the main function will block until the thread function returns to the main function after execution, and the main function will continue to run. If you don't want the thread function to return to the main function, you can also separate the thread function, which is to run the thread in the background. Or daemon thread.

	if (t1.joinable())
	{
		t1.join();
	}

The joinable() function checks whether t1 still needs to wait. It is used to judge whether join() or detach() can be called. It can return true, but not false
Note that the same thread cannot call Jeon () and detach() at the same time -- separating threads.

Because sometimes after creating a thread, the main thread takes other things. When calling the join function again, if the sub threads run fast or there is less code, the sub threads actually run out at this time, so there is no need to join. Therefore, use the joinable function to judge to avoid the sub threads still joining after the sub threads run.

Running threads in the background

#include <iostream>
#include <thread>

using namespace std;

void func2()
{
	cout << " -- thread-2 cout --" << endl;
}
void func3()
{
	cout << " -- thread-3 cout --" << endl;
}
int main()
{
	std::thread t1([]() {
		cout << " -- thread-1 cout --" << endl;	
	});
	std::thread t2(func2);
	std::thread t3{func3};
	cout << "-- main cout  --" << endl;
	if (t1.joinable())
	{
		t1.join();
	}
	
	t2.detach();
	if (t3.joinable())
	{
		t3.detach();
	}
	
	return 0;
}

In fact, t2 and t3 in this code are separate threads, which means that the sub thread will not return to the main thread after running. Even if the main thread runs, the sub thread can still run, and the above error will not occur. This situation is generally called a child thread as a daemon thread. Of course, the main thread will not be notified when the sub thread runs out. Equivalent to two threads not interfering with each other.

Pass parameters to thread function

To summarize the example of passing parameters to a thread:

Total code: analyze the examples one by one:

#include <iostream>
#include <thread>
#include <string>

using namespace std;

void func2(int i)
{
	std::cout << " -- thread-2 i = " << i << " cout --" << endl;
}
void func3(string& str)
{
	std::cout << " -- thread-3 str = " << str << " cout --" << endl;
}
void func4(int i ,string* sstr,string& str)
{
	std::cout << " -- thread-3 i = " << i << " sstr = " << *sstr << " str = " << str << " cout --" << endl;
}
int main()
{
	int a = 10;
	string str = "qwe";
	string* pstr = &str;
	const string* cpstr = &str;
	string& ppstr = str;
	const string& cssstr = "zxc";
	std::thread t1([](const string* sstr) {
		std::cout << " -- thread-1 sstr = " << *sstr << " cout --" << endl;
	}, cpstr);
	std::thread t2(func2,a);
	std::thread t3{ func3,ref(str) };
	std::thread t4([](const string& ssstr) {
		std::cout << " -- thread-4 ssstr = " << ssstr << " cout --" << endl;
	}, cref(cssstr));
	std::thread t5{ func4,a,pstr,ref(ppstr) };

	std::cout << "-- main cout  --" << endl;
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.detach();
	}
	
	if (t3.joinable())
	{
		t3.detach();
	}
	if (t4.joinable())
	{
		t4.join();
	}
	if (t5.joinable())
	{
		t5.join();
	}
	return 0;
}

I have run the above code, no problem, but it is messy. The following types are analyzed one by one.

Pass a parameter

Value transmission

t1 is the demo of an anonymous function passing values, and t2 is the demo of an ordinary function passing values

#include <iostream>
#include <thread>
#include <string>
using namespace std;
void func(int i)
{
	std::cout << " -- thread-2 i = " << i << " cout --" << endl;
}
int main()
{
	int a = 10;
	std::thread t1([](int a) {
		std::cout << " -- thread-1 a = " << a << " cout --" << endl;
	}, a);
	std::thread t2(func, a);
	std::cout << "-- main cout  --" << endl;
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.detach();
	}
	return 0;
}


Pass reference

It should be emphasized that ref() is a reference or cref() is a const reference function when a thread passes a reference. If the reference is passed without these two functions, an error will be returned. Because thread passes parameters with an R-value.

Obviously, there is an R-value reference, so how do we pass an l-value? std::ref and std::cref solve this problem well.
std::ref can wrap values passed by reference.
std::cref can wrap the value passed by const reference.

#include <iostream>
#include <thread>
#include <string>
using namespace std;
void func(string& str)
{
	std::cout << " -- thread-2 str = " << str << " cout --" << endl;
}
int main()
{
	string a = "qwe";
	string& str = a;
	std::thread t1([](string& str) {
		std::cout << " -- thread-1 a = " << str << " cout --" << endl;
	}, ref(str));
	std::thread t2(func, ref(str));
	std::cout << "-- main cout  --" << endl;
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.detach();
	}
	return 0;
}

Pass pointer

#include <iostream>
#include <thread>
#include <string>
using namespace std;
void func(string* str)
{
	std::cout << " -- thread-2 str = " << *str << " cout --" << endl;
}
int main()
{
	string a = "qwe";
	string* str = &a;
	std::thread t1([](string* str) {
		std::cout << " -- thread-1 a = " << *str << " cout --" << endl;
	}, str);
	std::thread t2(func, str);
	std::cout << "-- main cout  --" << endl;
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.detach();
	}
	return 0;
}


Pass multiple parameters

#include <iostream>
#include <thread>
#include <string>
using namespace std;
void func(int i, string* pstr, string& sstr)
{
	std::cout << " -- thread-2 i = " << i << " pstr = " << *pstr << " sstr = " << sstr << " cout --" << endl;
}
int main()
{
	int i = 10;
	string a = "qwe";
	string* pstr = &a;
	string& sstr = a;
	std::thread t1([](int i, string* pstr, string& sstr) {
		std::cout << " -- thread-1 i = " << i << " pstr = " << *pstr << " sstr = " << sstr << " cout --" << endl;
	}, i, pstr, ref(sstr));
	std::thread t2(func, i, pstr, ref(sstr));
	std::cout << "-- main cout  --" << endl;
	if (t1.joinable())
	{
		t1.join();
	}
	if (t2.joinable())
	{
		t2.detach();
	}
	return 0;
}

Transfer ownership of threads

Suppose you want to write a function that creates a thread running in the background, but returns the ownership of the new thread to the calling function instead of waiting for it to complete, or you want to do the opposite, create a thread and pass the ownership to the function waiting for it to complete. In either case, You need to transfer ownership from one place to another.

#include <iostream>
#include <thread>
#include <string>

using namespace std;

void func()
{
	cout << "thread1" << endl;
}
int main()
{
	thread t1(func);
	thread t2 = move(t1);
	if (t2.joinable())
	{
		t2.join();
	}
	if (t1.joinable())
	{
		t1.join();
	}
	return 0;
}

As a result, t2 owns the ownership of t1 thread, while t1 points to nullptr. Therefore, t2 The joinable() function returns true, while t1 The function of joinable () returns false and does not execute t1 Join() function. Because there is no t1 thread in a sense.

Select the number of threads at run time

The number of threads to run is less than the number of hardware threads. Over and over subscription. Because context switching will occupy threads and reduce performance.

Generally, you start one less thread than the hardware thread, because there is already a main thread.

The CPU I use now is 4-core and 4-thread. So the returned value is 4

#include <iostream>
#include <thread>
#include <string>

using namespace std;

void func()
{
	cout << "thread1" << endl;
}
int main()
{
	thread t1(func);
	thread t2 = move(t1);
	if (t2.joinable())
	{
		t2.join();
	}
	if (t1.joinable())
	{
		t1.join();
	}
	int a = std::thread::hardware_concurrency();
	cout << "thread num = " << a << endl;
	return 0;
}

result

Identify thread

The thread identifier is of type std::thread::id and can be obtained in two ways. First, the identifier of the thread can be obtained through get in the std::thread object_ ID () member function. If the std::thread object has no associated execution thread, get_ The call of ID () returns an std::thread::id object of the default constructor, indicating that there is no thread.

In addition, the identifier of the current thread can be through std::this_thread::get_id(), which is also defined in the < thread > header file.

Objects of type std::thread::id can be copied and compared freely, otherwise they are of little use as identifiers. If two objects of type std::thread::id are equal, they represent the same thread, or both have a value of "no thread".

#include <iostream>
#include <thread>
#include <string>

using namespace std;

void func()
{
	cout << "thread1" << endl;
}
int main()
{
	thread t1(func);
	thread t2 = move(t1);
	cout << "get_t1_thread_id = " << t1.get_id() << endl;
	cout << "get_t2_thread_id = " <<  t2.get_id() << endl;
	if (t2.joinable())
	{
		t2.join();
	}
	if (t1.joinable())
	{
		t1.join();
	}	
	return 0;
}

#include <iostream>
#include <thread>
#include <string>

using namespace std;

void func()
{
	cout << "thread1 id = " << std::this_thread::get_id() << endl;
}
int main()
{
	thread t1(func);
	thread t2 = move(t1);
	cout << "get_t1_thread_id = " << t1.get_id() << endl;
	cout << "get_t2_thread_id = " <<  t2.get_id() << endl;

	cout << "2_get_t1_thread_id = " << std::this_thread::get_id() << endl;	
	if (t2.joinable())
	{
		t2.join();
	}
	if (t1.joinable())
	{
		t1.join();
	}	
	return 0;
}

Summary:

td::this_thread::get_id() 

It is used in the function body to get the id of the current thread. Where to use it is to find the id of the current thread, but not the id of other threads.

cout << "get_t2_thread_id = " <<  t2.get_id() << endl;

This is to find the id of t2 thread. You can find the id of child thread in the main thread.

Topics: C++ Back-end