Thread management based on concurrency and multithreading

1. Start the thread and pass parameters to the thread:

#include<iostream>
#include<thread>
#include<chrono>
using namespace std;

//thread function
void func(int a, int b, int c)
{
    std::this_thread::sleep_for(std::chrono::seconds(3));
    cout << a << " " << b << " " << c << endl;
}

intmain()
{
    //Start the thread, create the thread object t1, and bind the thread function to func
    std::thread t1(func, 1, 2, 3);
    //Output the thread ID of t1
    std::cout << "ID:" << t1.get_id() << std::endl;
    //Wait for the execution of the t1 thread function to end
    t1.join();
    std::thread t2(func, 2, 3, 4);
    //Execute the thread function of t2 in the background, and will not cause an exception because the thread function has not been executed when the main function ends
    t2.detach();//Separate thread
    cout << "after t2 ,main is runing" << endl;
    //Use lambda expression as thread function
    std::thread t4([](int a, int b, int c)
        {
            // thread sleeps for 5 seconds
            std::this_thread::sleep_for(std::chrono::seconds(5));
            cout << a << " " << b << " " << c << endl;
        }, 4,5,6);
    t4.join();

    //Get the number of CPU cores
    cout << "CPU: " << thread::hardware_concurrency() << endl;
    //When adding the commented out statement below, an exception will be thrown, because the thread object ends before the thread function, it should be ensured that the life cycle of the thread object still exists when the thread function is executed.
    //std::thread t3(func, 3, 4, 5);

    return 0;
}

2. Transfer thread ownership

    A. The thread cannot be copied but can be moved. But after the thread is moved, the thread object will no longer represent any thread: that is, the ownership of the thread is transferred.

void some_function();
void some_other_function();
std::thread t1(some_function); // 1
std::thread t2=std::move(t1); // 2 t1 ownership is transferred to t2
t1=std::thread(some_other_function); // 3
std::thread t3; // 4
t3=std::move(t2); // 5
t1=std::move(t3); // 6 assignment will crash the program

    B. After explicitly using std::move() to create t2 ②, the ownership of t1 is transferred to t2. After that, t1 is no longer associated with the thread of execution; the function executing some_function is now associated with t2.

   C. Then, the thread associated with a temporary std::thread object starts ③. Why not explicitly call std::move() to transfer ownership? Because, the owner is a temporary object - the move operation will be called implicitly.

    D. t3 is created using the default construction method and is not associated with any execution thread. Call std::move() to transfer ownership of the thread associated with t2 to t3 ⑤. Because t2 is a named object, an explicit call to std::move() is required. Move operation⑤

    E. After completion, t1 is associated with the thread executing some_other_function, t2 is not associated with any thread, and t3 is associated with the thread executing some_function.

   F. The last move operation transfers the ownership of some_function thread ⑥ to t1. However, t1 already has an associated thread (the thread that executes some_other_function), so here the system directly calls std::terminate() to terminate the program and continue running. This is done (no exceptions are thrown, std::terminate() is a noexcept function) to guarantee consistent behavior with std::thread's destructor. In Section 2.1.1, it is necessary to explicitly wait for the thread to complete before the thread object is destructed, or to detach it; these conditions also need to be met when assigning way to "drop" a thread).

3. Determine the number of threads at runtime

    A. std::thread::hardware_concurrency() is a very useful function in the new version of the C++ standard library. This function will return the number of threads that can be concurrently in a program at the same time.

    B. Implemented a parallel version of std::accumulate. In the code, the overall work is divided into small tasks for each thread to do, and the minimum number of tasks is set to avoid generating too many threads. The program may throw an exception when the number of operations is zero. For example, if the std::thread constructor cannot start a thread of execution, an exception is thrown.

template<typename Iterator,typename T>
struct accumulate_block
{
	void operator()(Iterator first,Iterator last,T& result)
	{
	result=std::accumulate(first,last,result);
	}
};

template<typename Iterator,typename T>
T parallel_accumulate(Iterator first,Iterator last,T init)
{
	unsigned long const length=std::distance(first,last);
	if(!length) // 1: If the input is empty, you will get the value of init
	{
		return init;
	}
	unsigned long const min_per_thread = 25;
	
	unsigned long const max_threads = (length+min_per_thread-1)/min_per_thread; // 2
	
	unsigned long const hardware_threads=std::thread::hardware_concurrency();
	
	unsigned long const num_threads= std::min(hardware_threads != 0 ? hardware_threads : 2, max_threads);//3
	
	unsigned long const block_size=length/num_threads; // 4
	std::vector<T> results(num_threads);
	std::vector<std::thread> threads(num_threads-1); // 5
	Iterator block_start=first;
	
	for(unsigned long i=0; i < (num_threads-1); ++i)
	{
		Iterator block_end=block_start;
		std::advance(block_end,block_size); // 6
		threads[i]=std::thread( // 7
		accumulate_block<Iterator,T>(),
		block_start,block_end,std::ref(results[i]));
		block_start=block_end; // 8
	}
	
	accumulate_block<Iterator,T>()(
				block_start,last,results[num_threads-1]); // 9
	
	std::for_each(threads.begin(),threads.end(),
					std::mem_fn(&std::thread::join)); // 10
					
	return std::accumulate(results.begin(),results.end(),init); //11
}

         (1): If the input range is empty ①, you will get the value of init

            (2): If there is more than one element in the range, it is necessary to divide the total number of elements in the range by the minimum number of tasks in the thread (block) to determine the maximum number of threads to start ②, which can avoid unnecessary computing resources. waste. For example, on a 32-core machine, only 5 numbers need to be calculated, but 32 threads are started.

         (3): Among the maximum calculation amount and the number of threads supported by the hardware, the smaller value is the number of startup threads ③. Because of frequent context switching

Will degrade thread performance, so you definitely don't want to start more threads than the hardware supports. When std::thread::hardware_concurrency() returns 0, you can choose a suitable number as your choice; in this example, I chose "2". You also don't want to start too many threads on a single-core machine, because that will degrade performance and may eventually make you give up using concurrency.

          (4): The number of elements processed in each thread is obtained by dividing the total number of elements in the range by the number of threads ④

     (5): Determine the number of threads, store intermediate results by creating a std::vector<T> container, and create a std::vector<std::thread> container for threads ⑤. It should be noted here that the number of threads started must be 1 less than num_threads, because there is already a thread (main thread) before starting.

       (6): Use a simple loop to start the thread: the block_end iterator points to the end of the current block ⑥, and starts a new thread to accumulate results ⑦ for the current block. When the iterator points to the end of the current block, start the next block ⑧. After all threads are started, the threads in ⑨ process the result of the final block. For uneven distribution, it doesn't matter how many elements there are in the block because you know which one the final block is. After accumulating the results of the final block, you can wait for std::for_each ⑩ to finish creating the thread (as you did in Listing 2.7), then use std::accumulate to accumulate all the results ⑪.

4: Thread identifier: The thread identifier type is std::thread::id, which can be retrieved in two ways. The first one can be obtained directly by calling the member function get_id() of the std::thread object. If the std::thread object is not associated with any thread of execution, get_id() will return the default-constructed value of std::thread::type, which means "no thread". Second, the thread ID can also be obtained by calling std::this_thread::get_id() (this function is defined in the <thread> header file) in the current thread.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324625172&siteId=291194637