What is the difference between future and shared_future?
In what cases we must use shared_future instead of future?
I was trying to find good documentation that will contrast these two features of C++11, and I could not find an answer (easy/readable at least) on the web.
This is my current understanding of the differences
future object could be queried only once for the get().
shared_future could be queried any number of times.
use case:
If multiple threads are dependent on the result of an asynchronous task, then we must use shared_future.
If the future object needs be queried multiple times in the same thread then we must use shared_future instead.
Any more information, gotchas or general guidelines are welcome...
The motivation for these two future types goes back to move semantics, move-only types, and the new C++11 feature to return move-only types from ordinary functions.
In C++98/03, if you wanted to return a type from a factory function:
A
make_A()
{
A a;
// ...
return a;
}
then A had to be CopyConstructible. Then, brand new in C++11, we can return A even if it is not CopyConstructible, it need only be MoveConstructible.
But what happens if you try to execute make_A concurrently, say using futures. Wouldn't it be a crime if you could only parallelize make_A if A is CopyConstructible?! You would have to give up one optimization while chasing another!
So future<R> only requires R to be MoveConstructible. But you can only get it once, because you're moving from the stored result.
But getting the same result for multiple threads is a real need too. So shared_future<R> allows that, but requires R to be CopyConstructible.
Related
In his excellent book "C++ Concurrency in Action" (2nd edition including C++17) Anthony Williams discusses the implementation of a thread-safe stack.
In the course of this, he proposes an adapter implementation of std::stack, which, among other things, would combine the calls of top() and pop() into one. The separation into 2 separate functions, however, was done for a reason in std::stack, namely to avoid losing data in the case that a potential copy made when returning the popped element to the caller throws an exception inside the copy constructor. When returning, the element will have already been popped off and is consequentially lost.
Instead of having a function T pop(), he proposes other variations of pop that would be able to remove the element off the stack and provide it to the caller in one operation, all of which come with their own problems, though.
The 1st alternative he proposes has the signature void pop(T&). The caller passes in a reference to a T and gets the popped off object that way. This way of doing it, however, comes with the problem that a T need be constructed prior to the call to pop, which might be an expensive operation, or it might not be possible to construct a T beforehand at all because necessary data might not be available yet at the time. Another problem the author mentions is that T might not be assignable, which would be required for this solution, though.
Now my question: Wouldn't all of the mentioned problems be solved if we passed a std::optional<T>& instead of a T&?
In that case, no instance of T would need to be constructed prior to the call to pop. Furthermore, assignability would not be required anymore either, since the object to be returned could be constructed into the std::optional<T> instance directly using its emplace function.
Am I missing something crucial here or am I right? If I am indeed right, I would be curious to know why this was not considered (for a good reason or just plainly an oversight?).
std::optional does solve all of the mentioned problems, and using it to control lifetime can be quite valuable, although it would appear a bit strange in
std::optional<T> o;
st.pop(o);
to have o always engaged.
That said, with a stupid scope-guard trick it's possible in C++17 to safely return T even without requiring no-throw-movability:
T pop() {
struct pop_guard {
C &c;
int u=std::uncaught_exceptions();
~pop_guard() {if(std::uncaught_exceptions()==u) c.pop_back();}
} pg{c};
return std::move(c.back());
}
(We could of course test for a throwing move and just move (perhaps twice) in its absence.)
However, what wasn't mentioned is that separate top and pop allows a T that isn't even movable, so long as the underlying container supports it. std::stack<std::mutex> works (with emplace, not push!) because std::deque doesn't require movability.
I have trouble finding any up-to-date information on this.
Do C++11 versions of STL containers have some level of thread safety guaranteed?
I do expect that they don't, due to performance reasons. But then again, that's why we have both std::vector::operator[] and std::vector::at.
Since the existing answers don't cover it (only a comment does), I'll just mention 23.2.2 [container.requirements.dataraces] of the current C++ standard specification which says:
implementations are required to avoid data races when the contents of the contained object in different elements in the same sequence, excepting vector<bool>, are modified concurrently.
i.e. it's safe to access distinct elements of the same container, so for example you can have a global std::vector<std::future<int>> of ten elements and have ten threads which each write to a different element of the vector.
Apart from that, the same rules apply to containers as for the rest of the standard library (see 17.6.5.9 [res.on.data.races]), as Mr.C64's answer says, and additionally [container.requirements.dataraces] lists some non-const member functions of containers that can be called safely because they only return non-const references to elements, they don't actually modify anything (in general any non-const member function must be considered a modification.)
I think STL containers offer the following basic thread-safety guarantee:
simultaneous reads of the same object are OK
simultaneous read/writes of different objects are OK
But you have to use some form of custom synchronization (e.g. critical section) if you want to do something different, like e.g. simultaneous writes on the same object.
No. Check out PPL or Intel TBB for thread safe STL-like containers.
Like others have noted they have usual "multiple reader thread safety" but that is even pre C++11. Ofc this doesnt mean single writer multiple readers. It means 0 writers. :)
Are there any examples of the absence or presence of const affecting the concurrency of any C++ Standard Library algorithms or containers? If there aren't, is there any reason using the const-ness in this way is not permitted?
To clarify, I'm assuming concurrent const access to objects is data-race free, as advocated in several places, including GotW 6.
By analogy, the noexcept-ness of move operations can affect the performance of std::vectors methods such as resize.
I skimmed through several of the C++17 concurrent Algorithms, hoping to find an example, but I didn't find anything. Algorithms like transform don't require the unary_op or binary_op function objects to be const. I did find for_each takes a MoveConstructible function object for the original, non execution policy version, and take a CopyConstructible function object for the C++17 execution policy version; I thought this might be an example where the programmer was manually forced to select one or the other based on if the function object could be safely copied (a const operation)... but at the moment I suspect this requirement is just there to support the return type (UnaryFunction vs. void).
For the case when std::promise<> is instantiated with a non reference type, why does the set_value() method have two distinct overloads as opposed to one pass by value overload?
so instead of the following two
std::promise::set_value(const Type& value);
std::promise::set_value(Type&& value);
just one
std::promise::set_value(Type value);
This has at least the following two benefits
Enable users to move the value into the promise/future when they want, since the API argument is a value type. When copying is not supported it is obvious that the value is going to be copied. Further when the expression being passed into the function is a prvalue it can be completely elided easily by the compiler (especially so in C++17)
It conveys the point that the class requires a copy of the value a lot better and succinctly than two overloads which accomplish the same task.
I was making a similar API (as far as ownership is concerned) and I was wondering what benefits the design decision employed by the C++ standard library has as opposed to what I mentioned.
Thanks!
Passing an argument by value if it needs to be "transferred" unconditionally and then moving from it is a neat little trick, but it does incur at least one mandatory move. Therefore, this trick is best in leaf code that is used rarely or only in situations that are completely under the control of the author.
By contrast, a core library whose users and uses are mostly unknown to the author should not unnecessarily add avoidable costs, and providing two separate reference parameter overloads is more efficient.
In a nutshell, the more leaf and user you are, the more you should favour simplicity over micro-optimizations, and the more library you are, the more you should go out of your way to be general and efficient.
I am developing an application where I need to queue up some move-only types and I need fast write-access to the beginning and the end of the container (mainly adding elements fast).
At first glance I wanted to use std::deque<T> but it requires that T is copy-constructible, so it won't do the job.
I am now considering std::vector, but I'm worried that adding elements to the beginning of the vector would be really slow because of reallocating all the stuff.
Any suggestions on such container?
Note on operations I need (on std::deque):
emplace_back
emplace_front
pop_front
empty
front
These are the operations used currently (my implementation now uses std::shared_ptr to make move-only types copyable)
The exact type I need to queue is the move-only version of std::function<void()>. If I try the move-only version with std::deque I get the following compiler errors (clang++):
error: use of deleted function ‘std::packaged_task<_Res(_ArgTypes
...)>::packaged_task(const std::packaged_task<_Res(_ArgTypes ...)>&)
[with _Res = void; _ArgTypes = {}]’ In file included from
/home/superuser/Desktop/thread_pool/thread_pool.hpp:32:0,
from /home/superuser/Desktop/thread_pool/test.cpp:1: /usr/include/c++/6/future:1513:7: note: declared here
packaged_task(const packaged_task&) = delete;
Note that you see std::packaged_task, because it is moved into a lambda wrapped by std::function<void()>.
This is a classic example of why a [MCVE] is so useful.
std::function<void()> fun = std::move(queue.front());
The above won't compile with a non-copyable content in the queue. But the queue works fine. std::deque solves your problem.
std::function requires its contents to be copyable. Even if you never move it, it requires it be copyable. std::function uses type erasure, so "how to copy" the contents is stored when you store something in it.
Here is a move-only std::function that does not do the small buffer optimization I wrote on SO two years ago.
Today I would write it differently. I would split the type erasure from the storage, and write a separate SBO storage type, then join them together to write task<Sig>.
Amusingly, packaged_task<void(Args...)> is a type erased SBO move-only wrapper for packaged_task<R(Args...)>. But it does much more, so I would avoid using it.
Now, the rules for std containers with regards to the requirements for their content have varied, with the standard regularly getting more liberal. At one point a bunch of requirements where placed on types even if it wasn't used; the current standard states that these requirements are on a per-method basis. Many compilers enforced those more liberal requirements before the standard moved (because there was little need to be strict, the standard did not demand it), so even in compilers prior to the liberalization it wasn't a problem. I am uncertain if this liberalization occurred in std::deque by C++11 or not; this is, however, an example of "if it works in your compiler, use it, because future compilers are going to support it".