std::execut...llel_policy,(3)

๐Ÿ“ฆ version 2024.07.05, in nixpkgs unstable, opensuse-leap 16.0

๐Ÿ’… NAME ๐Ÿ’…

Okay bestie, welcome to std::execut...llel_policy,(3) โ€” thatโ€™s the page title โ€” from the C++ Standard Libary manual (yes, โ€œLibaryโ€ is literally in the header, not me ๐Ÿ˜ญ).

The NAME section is giving exact-and-slightly-chaotic: std::execution::sequenced_policy,std::execution::parallel_policy, - std::execution::sequenced_policy,std::execution::parallel_policy,. Commas included, periodt. โœจ


๐ŸŒธ Synopsis ๐ŸŒธ

Defined in header <execution>:

class sequenced_policy { /* unspecified */ };            (1) (since C++17)
class parallel_policy { /* unspecified */ };             (2) (since C++17)
class parallel_unsequenced_policy { /* unspecified */ }; (3) (since C++17)
class unsequenced_policy { /* unspecified */ };          (4) (since C++20)
  1. sequenced_policy โ€” this is the execution policy type used as a unique type to disambiguate parallel algorithm overloading, and it requires that a parallel algorithmโ€™s execution may not be parallelized. The invocations of element access functions in parallel algorithms invoked with this policy (usually specified as std::execution::seq) are indeterminately sequenced in the calling thread. No sneaky parallelism here, bestie.
  2. parallel_policy โ€” same unique-type-to-disambiguate-parallel-algorithm-overloading energy, but this one indicates that a parallel algorithmโ€™s execution may be parallelized. The invocations of element access functions in parallel algorithms invoked with this policy (usually specified as std::execution::par) are permitted to execute in either the invoking thread or in a thread implicitly created by the library to support parallel algorithm execution. Any such invocations executing in the same thread are indeterminately sequenced with respect to each other. Multithreading era ๐Ÿ’…
  3. parallel_unsequenced_policy โ€” again, a unique type used to disambiguate parallel algorithm overloading, and it indicates that a parallel algorithmโ€™s execution may be parallelized, vectorized, or migrated across threads (such as by a parent-stealing scheduler). The invocations of element access functions in parallel algorithms invoked with this policy are permitted to execute in an unordered fashion in unspecified threads, and unsequenced with respect to one another within each thread. Chaotic-good execution vibes.
  4. unsequenced_policy โ€” unique type, disambiguation duties, and it indicates that a parallel algorithmโ€™s execution may be vectorized, e.g., executed on a single thread using instructions that operate on multiple data items. Vectorized and cute.

During the execution of a parallel algorithm with any of these execution policies, if the invocation of an element access function exits via an uncaught exception, std::terminate is called, but the implementations may define additional execution policies that handle exceptions differently.


โœจ Notes โœจ

When using parallel execution policy, it is the programmerโ€™s responsibility to avoid data races and deadlocks:

int a[] = {0, 1};
std::vector<int> v;
std::for_each(std::execution::par, std::begin(a), std::end(a), [&](int i)
{
    v.push_back(i * 2 + 1); // Error: data race
});
std::atomic<int> x {0};
int a[] = {1, 2};
std::for_each(std::execution::par, std::begin(a), std::end(a), [&](int)
{
    x.fetch_add(1, std::memory_order_relaxed);
    while (x.load(std::memory_order_relaxed) == 1) { } // Error: assumes execution order
});
int x = 0;
std::mutex m;
int a[] = {1, 2};
std::for_each(std::execution::par, std::begin(a), std::end(a), [&](int)
{
    std::lock_guard<std::mutex> guard(m);
    ++x; // correct
});
Bestie, those comments are not decorative. // Error: data race and // Error: assumes execution order are literally the red flags. Do not collect them like Pokรฉmon. ๐Ÿ’€

Unsequenced execution policies are the only case where function calls are unsequenced with respect to each other, meaning they can be interleaved. In all other situations in C++, they are indeterminately-sequenced (cannot interleave). Because of that, users are not allowed to allocate or deallocate memory, acquire mutexes, use non-lockfree std::atomic specializations, or, in general, perform any vectorization-unsafe operations when using these policies (vectorization-unsafe functions are the ones that synchronize-with another function, e.g. std::mutex::unlock synchronizes-with the next std::mutex::lock).

int x = 0;
std::mutex m;
int a[] = {1, 2};
std::for_each(std::execution::par_unseq, std::begin(a), std::end(a), [&](int)
{
    std::lock_guard<std::mutex> guard(m); // Error: lock_guard constructor calls m.lock()
    ++x;
});
That std::lock_guard moment is a no-no with par_unseq. The comment says it all: // Error: lock_guard constructor calls m.lock().

If the implementation cannot parallelize or vectorize (e.g. due to lack of resources), all standard execution policies can fall back to sequential execution.


๐Ÿ‘€ See also ๐Ÿ‘€

These are the global execution policy objects (constant) that go with this page:

  • seq โ€” global execution policy object (constant) (C++17)
  • par โ€” global execution policy object (constant) (C++17)
  • par_unseq โ€” global execution policy object (constant) (C++17)
  • unseq โ€” global execution policy object (constant) (C++20)

Sign-off time, bestie: this page came from http://cppreference.com, dated 2024.06.10 โ€” go forth and parallelize responsibly, mwah ๐Ÿ’–โœจ๐ŸŽ€

๐Ÿ“ฆ where this page comes from