Recently in the C++ STL library multithreading part, basically finished, now to do a summary.

I. Advanced interface

STD ::async()

The thread returns STD :: Future

Shared_future: STD :: shareD_future

2. Low-level interfaces

Class STD ::thread

2. The thread returns the result: Promise

Thread pool: Class packaged_task

Mutex and Lock

1. Mutex:

(1) concept

(2) the role

2, Lock:

(1) concept

(2) the role

Condition Variable

(1) concept

(2) the role

 

Let’s learn one by one.

I. Advanced interface

std::async() & std::future

1. Async () is used to start a function asynchronously in a new thread immediately. That is, a thread start function. Its form is as follows:

std::aysnc(func1) // No arguments
Copy the code

Async () : async(); async() : async();

void func1(int arg1,int arg2)
{
    std::cout<<arg1<<std::endl;
    std::cout<<arg2<<std::endl;
}

std::aysnc(func1,arg1,arg2) Pass arg1,arg2 to func1;
Copy the code

3, STD ::aysnc() returns a value of type STD :: Future object. In STD :: Future object, we can retrieve thread return values or exception information. In addition, the specialization of the STD :: Future Object type is consistent with the return value of the thread function. The form is as follows:

void func1(int A = 0);
int func2(a);


int main(a)
{
	std::future<void> func1_res(std::async(print_A,10)); 
    //func1 returns type void, and future Object is also of type void

	std::future<int> func2_res(std::async(print_B));
    Func2 returns type int, and the future object is also of type int
}
Copy the code

4, Specify STD :: aySNc () launch strategy

STD :: Async has two main policies:

1, STD ::launch::async: Attempts to initiate an asynchronous call immediately, and returns a STD ::system_error if the call cannot be made here

2, STD ::launch::deferred: delays the start of the thread until we manually call future::get().

The following is an example:

#include <future>
#include <ctime>
#include <iostream>
#include <Windows.h>

void func1(a)
{
	std::cout << "func1 start!" << std::endl;
}
void func2(a)
{
	std::cout << "func2 start!" << std::endl;
}

int main(a)
{
    //f1 starts here, func1 start!
	std::future<void> f1(std::async(std::launch::async, func1));
    // F2 is not started here due to launch strategy
	std::future<void> f2(std::async(std::launch::deferred, func2));
	Sleep(3000);
	std::cout << "3 seconds later!" << std::endl;
    // Three seconds later, due to the call to Future ::get(), thread F2 is started and func2 start!
	f2.get(a);return 0;
}
Copy the code

std::shared_future

Shared_future is simply an object whose member function get() can be called multiple times.

Because the STD :: Future member function get() can only be called once, the second call can have unexpected behavior (effectively an error or no action at all). However, many times we want one thread to be used by multiple threads, and that’s where STD ::share_future comes in!

#include <future>
#include <iostream>
#include <string>
#include <thread>
#include <stdexcept>
#include <exception>

using namespace std;
int func1(a)
{
	std::cout << "Read Number: ";
	int num;

	std::cin >> num;
	if(! std::cin) {throw runtime_error("no number read");
	}
	return num;
}


void addOne(std::shared_future<int> SfObject)
{
	int num = SfObject.get(a); num +=1;
	std::cout << num << std::endl;
}

int main(a)
{
	std::shared_future<int> f = std::async(func1);
	auto f1 = std::async(addOne, f);
	auto f2 = std::async(addOne, f);

	f1.get(a); f2.get(a);return 0;
}
Copy the code

As you can see in the code above, STD ::shared_future<int> f’s member function STD ::get() is called multiple times. If we change share_Future Object to FutureObject, it won’t even compile.

 

2. Low-level interfaces

Class std::thread

The Class STD ::thread invocation interface is quite similar to STD ::async().

#include <iostream>
#include <thread>


void Print(int num)
{
	std::cout << "this is thread: "<< num << std::endl;
}


int main(a)
{

	std::thread t1(Print, 1); // Create thread 1
	std::thread t2(Print, 2); // Create thread 2
	t1.join(a);// Wait for thread 1 to finish
	t2.join(a);// Wait for thread 2 to finish
	std::cout << "this is main thread "<< std::endl;
	return 0;
}
Copy the code

As you can see, Class threads and STD ::async() are similar in their approach to thread creation and argument passing. It’s just that one is a class and one is a function.

However, there are a number of major differences between the two:

If we instantiate the Class thread object, the system will attempt to start the target function. If we fail to start the target function, the system will attempt to start the target function. STD ::system_error is thrown with the error resource_unavailable_try_again.

Class threads do not provide an interface for processing thread results

You must declare the state of the thread and wait for it to end (join()) or detach()).

4. If main() terminates, all threads are terminated

 

std::promise

To be added.

 

Class packaged_task

The Class packaged_task implementation allows you to freely control the start time of the start thread, which can be used to implement thread pools.

Let’s get straight to an example:

#include <iostream>
#include <thread>
#include <string>
#include <chrono>
#include <future>
#include <Windows.h>

void func1(a)
{
	std::cout << "Creating thread..." << std::endl;
}


int main(a)
{
	std::packaged_task<void(a)> task(func1);  // The thread task is created here, but the thread is not started immediately
	std::cout << "Sleep for 3 seconds" << std::endl;
	Sleep(3000); //sleep3 seconds, of course, this can be changed to anything you need
	task(a);// Start the thread after 3 seconds
	return 0;
}
Copy the code

Well, that’s pretty much the end of the thread startup and creation process so far, and we’ll end with a diagram.

            

(source: C++ standard library (translated by hou jie)

 

Mutex and Lock

First, we need to understand why mutex is needed. Since the threads are started at the same time, two different threads will output characters to a command window at the same time, which will lead to an unexpected situation, as shown in the following figure:

                                                              

As you can see, the output of the character is not as expected. So, when we have A variable called mutex that is being used in multiple threads at the same time, and thread A modifies mutex while thread B modifies mutex at the same time, unexpected things can happen.

So, we need mutex. At the same time, we need to Lock the variable modification process that will be called by multiple threads to ensure that the thread has exclusive access to resources during the locking process, so as to avoid the occurrence of unexpected things caused by the modification of a variable by multiple threads at the same time.

Using the character output code above as an example, how can we modify it to get the desired output? The answer is simple: lock a character and unlock it at the end of the output. Look at the code!

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mut;// Declare a mutex

void Print(int num)
{
	mut.lock(a);// Lock the output process
	std::cout << "this is thread: "<< num << std::endl;
	mut.unlock(a);/ / unlock
}


int main(a)
{

	std::thread t1(Print, 1);
	std::thread t2(Print, 2);
	t1.join(a); t2.join(a); std::cout <<"this is main thread "<< std::endl;
	return 0;
}
Copy the code

After this modification, we should get the desired output:

That’s it. We lock the common part of multi-threaded processing so that the thread monopolizes the resource, so that the resource can’t be used by other threads while the thread is modifying it. However, as more and more locking scenarios were used, we had more and more different requirements, so different locks were developed. There are also some problems.

(Various Mutex and their functions)

(Mutex Class operator function)

Next, let’s talk about some important ways to Lock

1, Class lock_guard

The simplest form of Lock is mutex.lock (). This method is easy to use, but many times people forget to unlock it (mutex.unlock ()). Class lock_guard is automatically locked when declared and destructively unlocked when out of scope.

Let’s look at the interface:

#include <iostream>
#include <thread>
#include <mutex>

std::mutex mut;

void Print(int num)
{
	std::cout << "this is thread_unlock: " <<num<< std::endl;/ / not locked
	{
		std::lock_guard<std::mutex> lg(mut);/ / lock
		std::cout << "this is thread: " << num << std::endl;
	}// Out of scope, automatic unlock
}


int main(a)
{

	std::thread t1(Print, 1);
	std::thread t2(Print, 2);
	t1.join(a); t2.join(a); std::cout <<"this is main thread " << std::endl;
	return 0;
}
Copy the code

The above code is serialized during the output of unlocked characters.

2, Class unique_lock

Class unique_locks are special in that they allow us to specify “when” and “how” to lock and result Mutex. In addition, in Class unique_locks, We can even use owns_lock() or bool() to see if Mutex is currently locked.

 

Condition Variable

In the practical application of multithreading, we always need one thread to wait for the result of another thread. Of course, the simplest and most crude way is to set a global bool ReadyFlag, and if the ReadyFlag state changes, the thread will process it.

But there are obvious drawbacks to this, and here’s an example to illustrate the problem

bool ReadyFlag{False};


void thread1(a)
{.....................// Process a large chunk of code, which takes some time.
    ReadyFlag = true; // If the conditions are met, ReadyFlag changes to true
}

void thread2(a)
{
    if(ReadyFlag) {.....................// Large chunks of processing code}}Copy the code

As you can see in the thread1 function, polling for the target condition is always done in Thread2 when dealing with large chunks of code, which can be very expensive.

In fact, what we want is for Thread1 to process a large number of operations and change the state of its ReadyFlag so that thread2 can start, and thread1 to wake thread2 up.

That’s what Condition variables are for.

Class Condition Variable member functions

So let’s look at an example and see how it works.

#include <condition_variable>
#include <mutex>
#include <future>
#include <iostream>

bool readyFlag;
std::mutex readyMutex;
std::condition_variable readyCondVar;

void thread1(a)
{
	std::cout << "<Return>" << std::endl;
	std::cin.get(a); {std::lock_guard<std::mutex> lg(readyMutex);
		readyFlag = true;
	}
	readyCondVar.notify_one(a);// If the condition is true, wake up the waiting person
}


void thread2(a)
{{std::unique_lock<std::mutex> ul(readyMutex);
		readyCondVar.wait(ul, [] {return readyFlag; });// Wait for the state of the condition variable
	}

	std::cout << "Done!" << std::endl;
}

int main(a)
{
	auto f1 = std::async(std::launch::async, thread1);
	auto f2 = std::async(std::launch::async, thread2);
	return 0;
}
Copy the code

Using Condition variables to wait for such communication between threads, the polling operation can be abandoned, saving CPU resources and time.

 

Ok, so far, I for C++ multithreading summary here ~ feel write a lot. But in fact, these are only superficial, just teach us how to use multithreading, the application of multithreading and various problems (such as the “deadlock” problem we often hear about) are not delved into, I will talk about these at a later time. Everyone, if you see any mistakes, please also point out, common progress ~

In addition, I also recommend everyone who has a dream to learn multithreading, you can go to see the “C++ standard library” (hou jie translated) this book, in the face of multithreading calls are very detailed explanation. Thank you, everybody. See here.