C + + Concurrent programming __linux

Source: Internet
Author: User

C++11 has many excellent features compared to previous versions, such as lambda expressions, initialization lists, right value references, and automatic type inference. At the same time, the C++11 standard library now supports regular expressions, smart pointers, and multi-line threading.

But modern C + + is still weak in both parallel and asynchronous computing, especially compared to languages like C #. the need for Asynchrony

Why do I need to support asynchrony? multi-core processors are almost ubiquitous and distributed in the cloud, making computer architectures more parallel and distributed. Software programs are often increasingly made up of components that use multiple cores located on a single machine or across networks. Modern programming languages need to provide support for this parallelism.

At the same time, responsiveness (which is one of the principles of responsive programming) has become an increasingly indispensable quality of software.

Responsiveness means that the IO operation is not blocked while waiting for its completion. Do not block a worker thread on the server side, and let it continue to do other things, waiting for the next task when the operation is complete. The main thread or GUI threads are not blocked on the client, otherwise the program becomes unresponsive. Therefore, the ability to write asynchronous code is increasingly important for managing the latency of IO operations. For example, there is a rule in winrt that all IO-intensive APIs that take more than 50ms of time provide only asynchronous interfaces, not even traditional blocking interfaces can be invoked.

Next we look at what C + + currently offers to support parallel programming, and what are the new features of these methods. We can see the standard approach and the Windows-specific PPL framework provided by Microsoft. Simple Example

To make it easier to understand how to write asynchronous code in C + +, let's write a simple example: read a file and write its contents to another file.

#include <string> 
#include <vector> 
#include <fstream> 
#include <iostream> 
using namespace std; 

Vector<char> readFile (const string& Inpath) 
{ 
    ifstream file (inpath, ios::binary | ios::ate); 
    size_t length = (size_t) file.tellg (); 
    Vector<char> buffer (length); 
    FILE.SEEKG (0, Std::ios::beg); 
    File.read (&buffer[0], length); 
    return buffer; 
} 

size_t WriteFile (const vector<char>& buffer, const string& Outpath) 
{ 
    ofstream file (Outpath, ios::binary); 
    File.write (&buffer[0], buffer.size ()); 
    Return (size_t) FILE.TELLP (); 

The above function can write a simple function implementation to copy the contents of one file into another file and return the number of characters:

size_t sync_copyfile (const string& inFile, const string& outfile) 
{return 
    WriteFile ( InFile), outfile); 

Obviously, we want to execute the ReadFile and Writelfile functions sequentially. But it is necessary to block the waiting for them to finish. Of course, this is an artificial example, if the file is not very large this may not matter, if the file is large, we can use the cache and block copy method instead of filling the contents of the file to a large vector return. But ReadFile and WriteFile are IO-intensive functions that represent only a pattern of more complex IO operations. It is common to read data from the network in a real-world program, to return or write to it after conversion.

Let's take a look at how to implement the CopyFile operation asynchronously in standard C + +. task-based parallelism: Future and Promise

The C + + standard library provides some mechanisms to support concurrency. The first is Std::thread, as well as related synchronization objects (Std::mutex, std::lock_guards, std::condition_variables, etc.), which ultimately provide a convenient way to write "traditional" multithreaded concurrency code.

We need to modify the CopyFile: Create a new thread to perform the copy operation, and use Condition_variable to notify after the thread execution completes. However, using threads and locks to operate is more complex and cumbersome. Modern frameworks, such as TPL in. NET, provide more advanced abstractions in a task-based concurrency approach. A task represents an asynchronous operation that can run in parallel with other operations, and the system hides the specifics of its implementation.

C++11 Standard library header files are also provided for task-based (limited) support in promise and future. std::p romise and std::future analogy to tasks in. NET, or future in Java8. These two classes always appear in pairs, separating the calling function from the fetch execution result.

When we call an asynchronous function, the caller is not getting the result of the type T, but rather a Std::future object, which is a placeholder that will return a result at some point in the future.

Once we get future, we can continue to do other things, while the asynchronous task is also executing in a separate thread.

std::p Romise object represents the execution result of an asynchronous call to the caller, which is a channel that passes the asynchronous result to the caller. When the task completes, the callee assigns the result to a promise object by calling Promise::set_value.

A blocking function future::get () can be obtained when the caller finally needs the result. If the task is completed, the result can be used immediately, otherwise the calling thread will be suspended until the result is available.

The above CopyFile uses the modified version of future and promise:

#include <future> 

size_t future_copyfile (const string& inFile, const string& outfile) 
{ 
    std ::p romise<vector<char>> prom1; 
    std::future<vector<char>> fut1 = Prom1.get_future (); 
    Std::thread Th1 ([&prom1, InFile] () { 
        prom1.set_value (ReadFile (InFile)); 
    }); 

    std::p romise<int> prom2; 
    std::future<int> fut2 = Prom2.get_future (); 
    Std::thread Th2 ([&fut1, &prom2, outfile] () {Prom2.set_value (WriteFile ( 
        ), fut1.get)); 

    size_t result = Fut2.get (); 
    Th1.join (); 
    Th2.join (); 
    return result; 

Note that we have moved the execution of ReadFile and WriteFile to separate tasks, but we still need to set up and start the thread to run them. Also, the desired promise and future objects are captured in lambda expressions so that they can be used directly in lambda expressions. The first thread performs a read operation and fills the results into the vector type promise when the read completes. The second thread waits blocked, and the result is passed into the write function of this thread when the first thread read operation completes. Finally, when the write operation completes, the number of characters written is filled in the second future.

The main thread can effectively use this parallelism to do other things before invoking Future::get () to get the final result. Of course, the main thread still blocks if the read and write operation is not completed when the Future::get () is invoked. Packaged Tasks

You can simplify the above code slightly with Packaged_task. std::p ackaged_task is a container for tasks and their promise. Its template type T is a task function type (for example, for the above ReadFile function, T is the vector (const string&)). It is a callable type (defined operator ()) and automatically creates and manages a std::future for us: The original text here is std::p romise, but I think it is std::future) object.

size_t packagedtask_copyfile (const string& inFile, const string& outfile) 
{ 
    using Task_type_read = vector<char> (const string&); 
    Packaged_task<task_type_read> pt1 (readFile); 
    Future<vector<char>> fut1{pt1.get_future ()}; 
    Thread th1{Move (PT1), inFile}; 

    Using Task_type_write = size_t (const string&); 
    Packaged_task<task_type_write> pt2 ([&FUT1] (const string& path) {return 
        WriteFile (Fut1.get (), Path ); 
    }); 
    Future<size_t> fut2{pt2.get_future ()}; 
    Thread th2{Move (PT2), outfile}; 

    size_t result = Fut2.get (); 
    Th1.join (); 
    Th2.join (); 
    return result; 

(Translator Note: Promise encapsulates a data type, Packaged_task encapsulates a callable object, both of which have get_future () interface for future objects)

Note that you need to use the move () function to pass packaged_task to the thread because the Packaged_task type cannot be copied. Std::async

The code doesn't change much after using Packaged_task, but we still need to manually create the thread and decide which thread the task is running on.

If we use the Std::async function in the standard library, everything will be very simple. Passing in a lambda or functor,std::async returns the future that contains the result value of the execution. The modified version of the CopyFile function with Std::async () is as follows:

size_t async_copyfile (const string& inFile, const string& outfile) 
{ 
    Auto fut1 = Async (ReadFile, InFile) ; 
    Auto Fut2 = Async ([&FUT1] (const string& path) {return 
        WriteFile (Fut1.get (), path); 
    }, 
    outfile); 

    return Fut2.get (); 

To some extent Std::async () is equivalent to TPL task scheduling. It determines which thread is performing the task, or in a newly created new thread, or in a reused old thread.

You can also determine the startup policy by using the second parameter, "async" means to perform the task asynchronously, possibly on a different thread, and "deferred" means that the task will only begin when get () is called.

The advantage of Std::async is that all implementations, platform-related details are hidden. Looking at the header file in vs2013, you can see its implementation under the Windows platform, using the PPL equivalent to the. NET TPL (Parallel Patterns Library) internally. PPL

What is seen above are the contents of c++11 standardization. Objectively speaking, the use of future and promise is still limited, especially compared to C # and. Net.

The main limitation is that the future is not c++11 (composable). If you start multiple task parallel computations, we cannot implement blocking all future, waiting for either of them to complete, but at some point only one future returns. Similarly, there is no good way to combine a set of tasks into a sequence, with the result of each task as input to the next task. A combination of tasks enables the entire architecture to be non-blocking and event-driven. Really want to have a similar pattern of task continuations or async/await in C + +.

with PPL (also known as Concurrency Runtime) Microsoft is likely to break the bounds of standards and try a more complex implementation of the task library.

In the PPL, class concurrency::task (defined in

size_t ppl_copyfile (const string& inFile, const string& outfile) 
{ 
    concurrency::task<vector< char>> Tsk1 = Concurrency::create_task ([InFile] () {return 
        readFile (inFile); 
    }); 
    concurrency::task<size_t> Tsk2 = Concurrency::create_task ([&tsk1, outfile] () {return 
        WriteFile ( Tsk1.get (), outfile); 
    return Tsk2.get (); 

There are 2 task objects created here, initialized by two lambda expressions, representing read and write operations respectively.

Now we really don't have to think about threading issues. The PPL schedule determines where to run the task and how to manage a pool of threads. Note that I still have to manually reconcile the interaction between the two tasks: Task2 has task1 references and explicitly waits for the task1 to end before using its results. This is acceptable in simple examples like this, but if it is more task, more complex code, it will become cumbersome. Task Continuation (tasks continuations)

Unlike future, the PPL task support is combined by continuation. The Task::next method can extend a task. When the current order task completes, it returns its result and calls the successor task.

This time using task continuation rewrite CopyFile:

size_t ppl_then_copyfile (const string& inFile, const string& outfile) 
{ 
    concurrency::task<size_t > result =  
    concurrency::create_task ([InFile] () {return 
        readFile (inFile); 
    }). Then ([outfile] (const vector<char>& buffer) {return 
        WriteFile (buffer, outfile); 
    }); 

    return Result.get (); 

This piece of code is very concise. We split the logic of the copy function into two separate parts (tasks) that run on any thread, and are run by a task scheduler.

The implementation of the above CopyFile will still block at the end to get the final result, but in a real program you can just return the Task object, insert it into the program logic, and add it to a task continuation to asynchronously manipulate its results. For example:

Concurrency::task<size_t> ppl_create_copyfile_task (const string& inFile, const string& outFile) 
{ return 
    Concurrency::create_task ([InFile] () {return 
        readFile (inFile); 
    }). Then ([outfile] (const vector<char>& buffer) {return 
        WriteFile (buffer, outfile);} 
    ); 
... 
Auto Tcopy = Ppl_create_copyfile_task (InFile, outfile). then ([] (size_t written) { 
    cout << written << Endl ; 
}); 
... 

PPL also provides other ways to combine tasks, Task::wait_all and task::wait_any, which are useful for managing a set of tasks that run in parallel.

For a set of Task,when_all, a second task is created after all tasks have been completed (joins are implemented internally). Instead, When_any creates a second task at the completion of any task, which is useful in some places, such as restricting the number of concurrent tasks and starting a new task only after a task completes.

The above is just the tip of the PPL. The PPL provides a very rich set of functions, almost as much as its managed version. Some scheduling classes are also provided to implement important tasks such as managing the work thread pool, assigning task to line thread, and so on. Please see here for details. c++17 Outlook

Soon we would like to see some implementations in the PPL described above in the C + + standard. There is already a document N3857 written by Niklas Gustaffson and others, which suggests some standard changes. In particular, Future::then is provided, Future::when_any and future::when_all implement future combinations, which have the same semantics as the PPL. For the previous example, the new future can be easily written as follows:

Future<size_t> future_then_copyfile (const string& inFile, const string& outfile)
{return
    async ([InFile] () {return
        readFile (inFile);
    }). Then ([outfile] (const vector<char>& buffer) {return
        WriteFile (buffer, outfile);}
    );

There is also a function future::is_ready that is used to test whether future is finished before calling the blocking function get (), future::unwrap to manage the nesting future (future Returns or future). Of course, all the details of this scenario can be found in the file provided above.

This is the perfect solution. Not exactly. Experience in. NET tells us that task based asynchronous code is still difficult to write, debug, and maintain. Sometimes it's very difficult. This is why some new keywords have been added to C # 5.0 in order to manage asynchronous tasks more conveniently through the async/await mode. Is there a similar approach in the C + + world?

"https://paoloseverini.wordpress.com/2014/04/07/concurrency-in-c11/, translation is not good, I hope to forgive"

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.