B. Stroustrup - The C++ Programming Language (794319), страница 32
Текст из файла (страница 32)
If the value couldn’t be computed,get() might throw an exception (from the system or transmitted from the task from which we weretrying to get() the value).Section 5.3.5.1futureand promise121The main purpose of a promise is to provide simple ‘‘put’’ operations (called set_value() andset_exception()) to match future’s get(). The names ‘‘future’’ and ‘‘promise’’ are historical; pleasedon’t blame me. They are yet another fertile source of puns.If you have a promise and need to send a result of type X to a future, you can do one of twothings: pass a value or pass an exception. For example:void f(promise<X>& px) // a task: place the result in px{// ...try {X res;// ...
compute a value for res ...px.set_value(res);}catch (...) {// oops: couldn’t compute res// pass the exception to the future’s thread:px.set_exception(current_exception());}}The current_exception() refers to the caught exception (§30.4.1.2).To deal with an exception transmitted through a future, the caller ofcatch it somewhere. For example:get()must be prepared tovoid g(future<X>& fx)// a task: get the result from fx{// ...try {X v = fx.get(); // if necessary, wait for the value to get computed// ... use v ...}catch (...) {// oops: someone couldn’t compute v// ...
handle error ...}}5.3.5.2 packaged_taskHow do we get a future into the task that needs a result and the corresponding promise into thethread that should produce that result? The packaged_task type is provided to simplify setting uptasks connected with futures and promises to be run on threads. A packaged_task provides wrappercode to put the return value or exception from the task into a promise (like the code shown in§5.3.5.1).
If you ask it by calling get_future, a packaged_task will give you the future correspondingto its promise. For example, we can set up two tasks to each add half of the elements of avector<double> using the standard-library accumulate() (§3.4.2, §40.6):122A Tour of C++: Concurrency and UtilitiesChapter 5double accum(double∗ beg, double ∗ end, double init)// compute the sum of [beg:end) starting with the initial value init{return accumulate(beg,end,init);}double comp2(vector<double>& v){using Task_type = double(double∗,double∗,double);// type of taskpackaged_task<Task_type> pt0 {accum};packaged_task<Task_type> pt1 {accum};// package the task (i.e., accum)future<double> f0 {pt0.get_future()};future<double> f1 {pt1.get_future()};// get hold of pt0’s future// get hold of pt1’s futuredouble∗ first = &v[0];thread t1 {move(pt0),first,first+v.size()/2,0};thread t2 {move(pt1),first+v.size()/2,first+v.size(),0};// star t a thread for pt0// star t a thread for pt1// ...return f0.get()+f1.get();// get the results}The packaged_task template takes the type of the task as its template argument (here Task_type, analias for double(double∗,double∗,double)) and the task as its constructor argument (here, accum).The move() operations are needed because a packaged_task cannot be copied.Please note the absence of explicit mention of locks in this code: we are able to concentrate ontasks to be done, rather than on the mechanisms used to manage their communication.
The twotasks will be run on separate threads and thus potentially in parallel.5.3.5.3 async()The line of thinking I have pursued in this chapter is the one I believe to be the simplest yet stillamong the most powerful: Treat a task as a function that may happen to run concurrently with othertasks. It is far from the only model supported by the C++ standard library, but it serves well for awide range of needs. More subtle and tricky models, e.g., styles of programming relying on sharedmemory, can be used as needed.To launch tasks to potentially run asynchronously, we can use async():double comp4(vector<double>& v)// spawn many tasks if v is large enough{if (v.size()<10000) return accum(v.begin(),v.end(),0.0);auto v0 = &v[0];auto sz = v.size();Section 5.3.5.3async()auto f0 = async(accum,v0,v0+sz/4,0.0);auto f1 = async(accum,v0+sz/4,v0+sz/2,0.0);auto f2 = async(accum,v0+sz/2,v0+sz∗3/4,0.0);auto f3 = async(accum,v0+sz∗3/4,v0+sz,0.0);123// first quarter// second quarter// third quarter// four th quar terreturn f0.get()+f1.get()+f2.get()+f3.get(); // collect and combine the results}Basically, async() separates the ‘‘call part’’ of a function call from the ‘‘get the result part,’’ and separates both from the actual execution of the task.
Using async(), you don’t have to think aboutthreads and locks. Instead, you think just in terms of tasks that potentially compute their resultsasynchronously. There is an obvious limitation: Don’t even think of using async() for tasks thatshare resources needing locking – with async() you don’t even know how many threads will be usedbecause that’s up to async() to decide based on what it knows about the system resources availableat the time of a call. For example, async() may check whether any idle cores (processors) are available before deciding how many threads to use.Please note that async() is not just a mechanism specialized for parallel computation forincreased performance.
For example, it can also be used to spawn a task for getting informationfrom a user, leaving the ‘‘main program’’ active with something else (§42.4.6).5.4 Small Utility ComponentsNot all standard-library components come as part of obviously labeled facilities, such as ‘‘containers’’ or ‘‘I/O.’’ This section gives a few examples of small, widely useful components:• clock and duration for measuring time.• Type functions, such as iterator_traits and is_arithmetic, for gaining information about types.• pair and tuple for representing small potentially heterogeneous sets of values.The point here is that a function or a type need not be complicated or closely tied to a mass of otherfunctions and types to be useful. Such library components mostly act as building blocks for morepowerful library facilities, including other components of the standard library.5.4.1 TimeThe standard library provides facilities for dealing with time.
For example, here is the basic way oftiming something:using namespace std::chrono;// see §35.2auto t0 = high_resolution_clock::now();do_work();auto t1 = high_resolution_clock::now();cout << duration_cast<milliseconds>(t1−t0).count() << "msec\n";The clock returns a time_point (a point in time). Subtracting two time_points gives a duration (aperiod of time). Various clocks give their results in various units of time (the clock I used measuresnanoseconds), so it is usually a good idea to convert a duration into a known unit. That’s what duration_cast does.124A Tour of C++: Concurrency and UtilitiesChapter 5The standard-library facilities for dealing with time are found in the subnamespace std::chrono in<chrono> (§35.2).Don’t make statements about ‘‘efficiency’’ of code without first doing time measurements.Guesses about performance are most unreliable.5.4.2 Type FunctionsA type function is a function that is evaluated at compile-time given a type as its argument orreturning a type.
The standard library provides a variety of type functions to help library implementers and programmers in general to write code that take advantage of aspects of the language,the standard library, and code in general.For numerical types, numeric_limits from <limits> presents a variety of useful information(§5.6.5). For example:constexpr float min = numeric_limits<float>::min();// smallest positive float (§40.2)Similarly, object sizes can be found by the built-in sizeof operator (§2.2.2).
For example:constexpr int szi = sizeof(int); // the number of bytes in an intSuch type functions are part of C++’s mechanisms for compile-time computation that allow tightertype checking and better performance than would otherwise have been possible. Use of such features is often called metaprogramming or (when templates are involved) template metaprogramming (Chapter 28). Here, I just present two facilities provided by the standard library: iterator_traits(§5.4.2.1) and type predicates (§5.4.2.2).5.4.2.1 iterator_traitsThe standard-library sort() takes a pair of iterators supposed to define a sequence (§4.5).
Furthermore, those iterators must offer random access to that sequence, that is, they must be randomaccess iterators. Some containers, such as forward_list, do not offer that. In particular, a forward_list is a singly-linked list so subscripting would be expensive and there is no reasonable wayto refer back to a previous element.