promise::set_value()
and promise::get_future()
should not raceSection: 32.10.6 [futures.promise], 32.10.10.2 [futures.task.members] Status: C++20 Submitter: Jonathan Wakely Opened: 2014-06-23 Last modified: 2021-02-25
Priority: 3
View other active issues in [futures.promise].
View all other issues in [futures.promise].
View all issues with C++20 status.
Discussion:
The following code has a data race according to the standard:
std::promise<void> p; std::thread t{ []{ p.get_future().wait(); }}; p.set_value(); t.join();
The problem is that both promise::set_value()
and
promise::get_future()
are non-const member functions which modify the
same object, and we only have wording saying that the set_value()
and
wait()
calls (i.e. calls setting and reading the shared state) are
synchronized.
get_future()
does not conflict with calling the various functions that
make the shared state ready, but clarify with a note that this does
not imply any synchronization or "happens before", only being free
from data races.
[2015-02 Cologne]
Handed over to SG1.
[2016-10-21, Nico comments]
After creating a promise or packaged task one thread can call get_future()
while another thread can set values/exceptions (either directly or via function call).
This happens very easily.
promise<string> p; thread t(doSomething, ref(p)); cout << "result: " << p.get_future().get() << endl;
AFAIK, this is currently UB due to a data race (calling get_future()
for the
promise might happen while setting the value in the promise).
promise<string> p; future<string> f(p.get_future()); thread t(doSomething, ref(p)); cout << "result: " << f.get() << endl;
but I would like to have get_future()
and setters be synchronized to avoid this UB.
vector<packaged_task<int(char)>> tasks; packaged_task<int(char)> t1(func); // start separate thread to run all tasks: auto futCallTasks = async(launch::async, callTasks, ref(tasks)); for (auto& fut : tasksResults) { cout << "result: " << fut.get_future().get() << endl; // OOPS: UB }
Again, AFAIK, this program currently is UB due to a data race. Instead, currently I'd have to program, which is a lot less convenient:
vector<packaged_task<int(char)>> tasks; vector<future<int>> tasksResults; packaged_task<int(char)> t1(func); tasksResults.push_back(t1.getFuture())); tasks.push_back(move(t1)); // start separate thread to run all tasks: auto futCallTasks = async(launch::async, callTasks, ref(tasks)); for (auto& fut : tasksResults) { cout << "result: " << fut.get() << endl; }
With my naive thinking I see not reason not to guarantee
that these calls synchronize (as get_future
returns an "address/reference"
while all setters set the values there).
Previous resolution [SUPERSEDED]:
This wording is relative to N3936.
Change 32.10.6 [futures.promise] around p12 as indicated:
future<R> get_future();-12- Returns: A
-?- Synchronization: Calls to this function do not conflict (6.9.2 [intro.multithread]) with calls tofuture<R>
object with the same shared state as*this
.set_value
,set_exception
,set_value_at_thread_exit
, orset_exception_at_thread_exit
. [Note: Such calls need not be synchronized, but implementations must ensure they do not introduce data races. — end note] -13- Throws:future_error
if*this
has no shared state or ifget_future
has already been called on apromise
with the same shared state as*this
. -14- Error conditions: […]Change 32.10.10.2 [futures.task.members] around p13 as indicated:
future<R> get_future();-13- Returns: A
-?- Synchronization: Calls to this function do not conflict (6.9.2 [intro.multithread]) with calls tofuture<R>
object that shares the same shared state as*this
.operator()
ormake_ready_at_thread_exit
. [Note: Such calls need not be synchronized, but implementations must ensure they do not introduce data races. — end note] -14- Throws: afuture_error
object if an error occurs. -15- Error conditions: […]
[2017-02-28, Kona]
SG1 has updated wording for LWG 2412. SG1 voted to move this to Ready status by unanimous consent.
[2017-03-01, Kona, SG1]
GeoffR to forward revised wording.
[2018-06, Rapperswil, Wednesday evening session]
JW: lets move on and I'll file another issue to make the wording better
BO: the current wording is better than what there before
JM: ACTION I should file an editorial issue to clean up on how to refer to [res.on.data.races]: raised editorial issue 2097
ACTION: move to Ready
[2018-11, Adopted in San Diego]
Proposed resolution:
This wording is relative to N4750.
Change 32.10.6 [futures.promise] around p12 as indicated:
future<R> get_future();-12- Returns: A
-?- Synchronization: Calls to this function do not introduce data races (6.9.2 [intro.multithread]) with calls tofuture<R>
object with the same shared state as*this
.set_value
,set_exception
,set_value_at_thread_exit
, orset_exception_at_thread_exit
. [Note: Such calls need not synchronize with each other. — end note] -13- Throws:future_error
if*this
has no shared state or ifget_future
has already been called on apromise
with the same shared state as*this
. -14- Error conditions: […]
Change 32.10.10.2 [futures.task.members] around p13 as indicated:
future<R> get_future();-13- Returns: A
-?- Synchronization: Calls to this function do not introduce data races (6.9.2 [intro.multithread]) with calls tofuture
object that shares the same shared state as*this
.operator()
ormake_ready_at_thread_exit
. [Note: Such calls need not synchronize with each other. — end note] -14- Throws: afuture_error
object if an error occurs. -15- Error conditions: […]