码迷,mamicode.com
首页 > 编程语言 > 详细

C++11 Concurrency

时间:2017-10-16 21:41:23      阅读:152      评论:0      收藏:0      [点我收藏+]

标签:environ   ttl   env   into   second   requests   mode   thread   ==   

Introduction

I‘m writing this article to unleash the various new features supported by C++11 as part of core language and library.Already I have posted an article on smart pointers in C++11. I‘ll continue to write on few other features as well in the upcoming articles.

In this article, we will discuss about the support for the concurrency in C++11. I‘m not (and never) better than Nicolai Josuttis ( Author : "The C++11 Standard Library" ) and Anthony Williams( Author : "C++11 Concurrency in Action" ). So I‘ll try to present briefly whatever I have learnt from these giants and concurrency is a topic which is boundless and you have to really go through the above books to know the complete details. However I hope, this will be an introduction and may be a good start point before you dive deep into the concurrency.

Background

As all of the moden processors are powered with multi cores , all modern applications try to exploit it to improve performance by doing multiple tasks in parallel. And there is no doubt that most of you might have come across the threading to do multi tasking in your applications. But you might have used the platform specific thread features like Pthreads etc.,

Programming with threads turns into night mare as it introduces lot of new issues which programmer might have not encountered during serial programming. Corrupting the data which is accessed by multiple threads with out any collaboration, deadlocks are some of them. Obviously these should be rectified and there are several ways to do it which we are not going to cover in this article. It is going to be part of thread synchronization technique.

In this article we are going to look how C++11 makes the programmer‘s life easy by making the threading as part of language feature. As this is part of language, there is no effort needed to port the application across the platforms.

The language provides high level features and low level features. High level features are much like a wrapper over the low level features which allows you to make use of them with out worrying much about the inner details. The programmer is free to use the low level features as well to have a better control.

High Level Interface

std::async

For a novice programmer it is always better to start with the high lever interfaces rather than struggling with the low level interfaces. So we will start with the high level interface std::async to start an asynchoronous task. std::async is an interface makes any callable object ( can be a function, lambda, function object or so called functor ) to run asynchronously in the background as a separate thread, if possible. What "if possible" ? Yes, we will come to it later.

Once the async is launched, it is associated with a std::future object.This is to hold the outshoot of your task, normally it is the return value of your funtion which is running in separate thread. Some of the functions may not return any thing at all. But still in this case also we may need the returned std::future object.Why? Wait, lets go step by step.

Lets see the code how to use std::async.

int Func1( )
{ 
   //To do some processing
   return result; 
} 
 
int Func2( )
{ 
  //To do some processing
   return result; 
} 
 
int main( )
{ 
  int iResult1 = Func1( ); 
  int iResult2 = Func2( ); 
  return 0; 
}

In the above code, Func1 is called first followed by Func2. Func2 will not be executed until Func1 completes its execution as the execution is serial. Now lets asynchronously call the function Func1.

int Func1( )
{ 
   //Do some processing
   return result; 
} 
 
int Func2( )
{ 
  //Do some processing
   return result; 
} 
 
int main( )
{
   //Start the Func1 in asynchronous mode and get the associated furture object
   std::future<int> fut = std::async(Func1);
 
   //Get the result from the future
   int iResult1 = fut.get();
 
   int iResult2 = Func2();
 
   return 0;
 }

In the above code we are launching the Func1 asynchronously using std::async. This will try to start executing the function asynchronously in a new thread. The future object associated with Func1 execution is returned and strored in fut.Once after that, the main (primary thread) continues its execution with function call Func2. This is a blocking call as it is executed by the same thread which executes the main function.

Why do we need future?

Future object is useful to

  • get the result of the called function. The result can be either a return value or can be an exeception ( if the called function throws something ).Future is a template type, specialized to the return type of the function.
  • ensure the passed function is called. This is improtant. Do you remember we said that "async tries to call the function asynchronously". If it is not called, future object can be used to force the execution when we need the result of the function to proceed further.

So it is always better to store the future object even if you are not interested in the return value of the function/the function has a void return type. Because we may need this future object to force the execution, if it not started by the async.

Future is a shared object to share the state between 2 different threads.Calling its get( ) method can result into one of the following

  • If the async has launched the functionality in a separate thread and completed its execution, get() may give you back the result.
  • If the functionality is launched and the execution is not finished yet, then get( ) is going to be a blocking call until the functionality is completed and return the result.
  • If functionality is not started yet due to some reason, get( ) behaves like a synchronous function call and blocks till it completes the execution.This is very important because, if the functionality cannot be started and the execution looks for the result of Func1, this is the only way to achieve it.
Async tries to execute in parallel????

Yes. Async tries to execute the given functionality in parallel by launching a new thread. But it may not succeed in doing so because

  • There are no more resources available to lauch a new thread
  • The programming environment does not support multi threading. 

So get( ) make sures that functionality gets called either asynchoronusly or synchronosuly ( at worst case ). If async  ( ) could not start the function in a separate thread, it will defer the call and executed when the user requests explicity by calling the get() method.

You can make use of lambda in place of function in the std::async

std::future<int> fut = std::async([ ] { } );

Launch Policy

You can explicity specify how the functionality has to be launched by std::async. std::async take the launch policy as a parameter in addition to the callable object.

std::launch::async:
  • With this launch policy we can force std::async to launch the funcionality only in asynchronous mode. If std::async cannot do this, the system will throw the system_error exception.
  • In this case, the destructor of the future object will block the execution until the passed function is executed and the future becomes ready ( This is proposed by Herb Sutter). But when I tried with VS2012, I could not find this feature.
std::launch::deferred:
  • This policy allows the user to do the lazy evaluation of a task. By using this launch policy, the called function is indefenitely deferred till the get() is called on the returned future object.The passed function will be called only if get() is called explicitly on the returned future. After creating the async,if you check the status of the returned future object, it will be std::future_status::deferred.

Handling Exceptions

  • Future object is intelligent enough to carry result ( in case of successful execution ) as well as exception ( in case the function throws any exception ). If the called function throws any exception and is not handling the same, then it will be propagated to the caller via future.
  • Calling get( ) method of future will give you back either result or exception. Caller can be perpared to handle the exceptions thrown by the background tasks.
Wait( ):

get( ) of future can be called only once. After that the future object becomes void, I mean the only valid operation that can be done after calling get( ) is destruction. Anyother call on the future object will result into undefined behavior.

Future provides another interface called wait( ) which provides the user with a way to wait for the background operation to complete. This forces the thread to start ( if it is not started already ) and waits for its completion. This can be called more than once.

There are 2 wait interfaces.

  • wait_for( ) : This is used to wait for a specified amount of time duration. You can make use of this interface if the time duration is absolute.
std::future<int> fut = std::async( func );
//I would like to wait for 500 millisecons before proceeding further 
fut.wait_for( std::chrono::millisecond(500) );
  •   wait_until( ) : Used to wait till a specified time point is reached. You can use this interface if you want to specify relative time duration.  

The above functions returns one of the following

  • future_status::deferred: The operation has not be started yet. No call to wait() or get( ) is being made to force the execution.
  • future_status::timeout: The background task is started and in progress but the specified time is elapsed.
  • future_status::ready: The task is being completed and you are ready to taste the result.  

Wait function can be used for polling by passing the duration as 0. This will start continously checking the state of the future object till it turns out to ready.

while( fut.wait_for( chrono::second(0) == future_status::ready )
{
   //Do proessinng further
}

    You should be careful with the loop above. Because the background task might have not been started at all. In this case, the while loop will never end. So make sure that the task is started and then poll for its result.

if( fut.wait_for( chrono::seconds(0) != future::status::deferred )
{
 
    while( fut.wait_for( chrono::second(0) == future_status::ready )
    {
      //Do proessing further
    }
}

    The other way to make sure the task is started while polling is by passing the async launch policy (std::launch::async ) explicity to std::async. Be careful about this kind of polling as it wastes the precious cpu time.

Task Parameters

How to pass the parameters to the task which is to be execute asynchronously? This can be done in 2 ways.

  •  By calling the lambda asynchronously and the lambda in turn call the callable object by passing the required parameters.
void Print(int num)
{
   int i = 0;
   for( ; i < num; ++i )
   {
      cput<<"Hello world"<<endl;
   }
}
 
cout<<"Enter the number : ";
int number;
cin>>number;
 
//Passing number by value as the capture clause is by value [=]
std::async( [=] { Print(number); } );
  •   We can pass the arguments directly via async interface.
void Print(int num)
{
   int i = 0;
   for( ; i < num; ++i )
   {
      cout<<"Hello world"<<endl;
   }
}
 
 
void main()
{
cout<<"Enter the number : ";
int number;
cin>>number;
 
//Passing number by value as the capture clause is by value (=)
std::async(Print,number );
}

In the above case number is passed by value.

Pass by reference

First lets see how to pass the arguments by reference. Let me rewrite the code used for the pass by value.

In case of lambdas, capture clause can be changed to reference.

void Print(int num)
{
  
}
cout<<"Enter the number : ";
int number;
cin>>number;
 
//Passing number by value as the capture clause is by value (=)
std::async( [&] { Print(number); } );

 The other one can be used with std::ref method.

void Print(int &num)
{
 
}
void main()
{
cout<<"Enter the number : ";
int number;
cin>>number;
 
//Passing number by value as the capture clause is by value (=)
std::async(Print,std::ref(number));
}

 You should be very cautious while using passing by reference method because

  • You are setting trap to yourself by "pass by reference" which induces the data race condition. The arguments become more susceptible for corruption unless you dont have proper synchronization in place.
  • Its your responsibility to keep the variable alive till the asynchronous operation is completed. Lets see the following code. Say the operation Foo is a long running task and the following piece of code is a real problem.
void Foo(int& num )
{
  //A long running task which uses the num
}
void main()
{ 
   cout<<"Enter the number : ";
   int number;
   cin>>number; 
   auto f = std::async( std::launch::async,Foo, std::ref(number) )
}

In the above code main ends before Foo is completed and number goes out of scope.

So as a thumb rule it is always better to pass the arguments by value instead of by reference to the async operations.

Low Level Interface

Now let‘s see the low level interfaces.

std::thread

The low level interface is std::thread to start a task in an asynchronous fashion.

The created thread can run in detached mode as well. In detached mode, the created thread can run even if

  • the thread object associated with it goes out of scope.
  • the main completes the execution before the created thread finishes its job.

The problem with the detached threads is there is no control over it and its the user‘s responsibility to make sure that it does not access any object which has gone out of scope. So again it is always better to pass by value, so that the threads will use their own copy instread of relying on the shared resources.

In case of async, the get() method is used to get the result/exception of the specified task. Similarly in case of std::threads, we can ensure that the task is completed before proceeding further by calling the join method. join() method is a blocking call, so the calling thread should wait for the task to complete its execution.

Either the thread should run in detached mode or you have to wait for the completion of the thread execution by calling the join method. Otherwise, the created thread will continue the execution where as the thread object associated with the thread will go out of scope and the program is aborted.

Thread Id

Each thread is assigned with a unique therad id. This is of type std::thread::id. But there is very little we can do with the obtained thread id like compare and output them. Ids of the terminated thread might be assigned to any of the newly created threads.

Promise

You may be wondering how to communicate between threads like passing the parameter to the function and getting back the result of the function executed by the thread.

Passing Parameters: Passing the parameters can be done like this:

void AsyncFunc(int x,int y )
{
    //Do the processing
} 
void main()
{
   std::thread t1(AsyncFunc,10,12);
   :
   :
   //Wait for the result of the async operation
   t1.join();
}

Return Value: Obviously by passing the parameter by reference we can get back the result of the executed function. As we discussed already, passing by reference is not recommeneded as it has inherent issues like keeping the passed varaible alive till the execution completion etc., There is another mechanism to get the result from the thread which is known as std::promise.

In case of std::async(), async takes care of sending the result and its our responsibility to get the result back and std::future is meant for it. So std::future is used to unpack the result in the caller where as packing is done by the async().

std::promise is used to set the outcome of the thread function which can be then used by the caller.The outcome can be the result/execption.

In nutshell,std::future is to retrieve the result and used by the caller. std::promise is to set the result by the created std::thread. Thatswhy std::future does not have any interface to set the value and std::promise does not have any interface to get the value.

Let‘s see a small example:

std::promise<long> g_prom;
void ThreadFun( )
{
	long Result = 0;
	int i = 0, j = 0;
    try
    {
	   //Do some long processing
	   for( ; i < 100; i++ )
	   {
		  j = 0;
		  for( ; j < 100; j++ )
		  {
		    	Result += ( i * j );
		  }
	   }
    }
    catch(std::exception e)
    {
        g_prom.set_exception(e);
    }
    
	//Set the result to the promise if the long running process is done
	g_prom.set_value( Result );

}

int main()
{
	//Get the future from the promise
	std::future<long> fut = g_prom.get_future();
	
	//Launch the thread
	std::thread t( ThreadFun );

	//Wait for the thread to complete by querying the result from the future
	t.join();

	long value = 0;
	try
	{
		value  = fut.get();			
        cout<<"Value = "<<value<<endl;
	}
	catch(...)
	{
       cout<<"Exception is thrown...."<<endl;
	}
}
</long></long>

In the above example, main starts the function ThreadFunc asynchronously using std::thread. Once after launching it, main waits for the result of ThreadFunc. ThreadFunc sets the result in the g_prom using set_value(), which is a global promise object which is accessible in ThredaFunc as well as in main. Promise has internally a shared state which is used to store the result/exception. In case of exception, set_exception method is used.

In main(), we are using the future object to get the result as that is the only way to get it. While calling get_future( ) of promise, it creates a future object using its shared state and returns it. So the state is shared between the promise and future. Whenever the value/exception is set in the promise object, it shared state become ready.

If the thread is still executed and when you try to call the get( ) of the future retrieved from promise, then it is blocking call. This execution is blocked till the shared state is ready.

So we have seen the various ways to start a concurrent task. But concurrent task has their own issues like data race, dead locks etc., C++11 provides thread synchronization mechanisms, atomic operations as well. We will discuss about them in the upcoming articles..

About code

I have not added any exhaustive code samples as these concepts can be explained with small code snippet itself. I have added the code snippets in place while dicussing about each feature. I have just added tiny examples as well. Make sure to use visual studio versions > VS12 to build the code.

Acknowledgement

I would like to thank Nicolai M.Josuttis for his book "The C++11 Standard Library" which explains the C++11 language and libarary features in a detailed manner. I used this book as a reference to write this article.

C++11 Concurrency

标签:environ   ttl   env   into   second   requests   mode   thread   ==   

原文地址:http://www.cnblogs.com/leslieai/p/7678342.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!