liveBook · Manning

Serial region

As explained in C++ Concurrency in Action, Second Edition

A portion of a program in which one thread performs the computation rather than multiple threads working concurrently.

A serial region is work performed by one thread rather than concurrently by several. In data processing, splitting an input block and writing a completed result are serial tasks surrounding parallel chunk processing. section 4.4.9

A basic barrier can coordinate serial work assigned to thread 0: other threads wait until it finishes preparing chunks, then all process their own chunks and synchronize before thread 0 writes the result. A flex_barrier can instead run a completion function on one arriving thread after all threads arrive. That function can write results and prepare the next block, leaving one synchronization point in the loop. It can also change the number of participants for the next cycle, so the program must ensure the correct number arrive. section 4.4.9 section 4.4.10

Processing data with flex_barrier

A completion function writes the current result, splits the next input block, and returns -1 to keep the participant count unchanged. The worker loop then contains only the parallel processing and a single synchronization point. section 4.4.10

Listing 4.27. Using std::flex_barrier to provide a serial region
void process_data(data_source &source, data_sink &sink) {
    unsigned const concurrency = std::thread::hardware_concurrency();
    unsigned const num_threads = (concurrency > 0) ? concurrency : 2;

    std::vector<data_chunk> chunks;

    auto split_source = [&] {                                  #1
        if (!source.done()) {
            data_block current_block = source.get_next_data_block();
            chunks = divide_into_chunks(current_block, num_threads);
        }
    };

    split_source();                                            #2

    result_block result;

    std::experimental::flex_barrier sync(num_threads, [&] {    #3
        sink.write_data(std::move(result));
        split_source();                                        #4
        return -1;                                             #5
    });
    std::vector<joining_thread> threads(num_threads);

    for (unsigned i = 0; i < num_threads; ++i) {
        threads[i] = joining_thread([&, i] {
            while (!source.done()) {                           #6
                result.set_chunk(i, num_threads, process(chunks[i]));
                sync.arrive_and_wait();                        #7
            }
        });
    }
}


!@%STYLE%@!
{"css":"{\"css\": \"font-weight: bold;\"}","target":"[[{\"line\":6,\"ch\":63},{\"line\":6,\"ch\":65}],[{\"line\":13,\"ch\":63},{\"line\":13,\"ch\":65}],[{\"line\":17,\"ch\":63},{\"line\":17,\"ch\":65}],[{\"line\":19,\"ch\":63},{\"line\":19,\"ch\":65}],[{\"line\":20,\"ch\":63},{\"line\":20,\"ch\":65}],[{\"line\":26,\"ch\":63},{\"line\":26,\"ch\":65}],[{\"line\":28,\"ch\":63},{\"line\":28,\"ch\":65}]]"}
!@%STYLE%@!

See the flex_barrier example, including the extracted block-splitting function and completion function. section 4.4.10

Serial work limits the performance benefit of adding processors: under Amdahl’s law, a one-third serial fraction caps theoretical speedup at three, even with infinitely many processors. section 8.4.2

How C++ Concurrency in Action, Second Edition covers it

How deep it goes
Explains barrier-based serial work and flex_barrier completion behavior, including participant-count changes and the need to match arrivals.
Examples
Uses chunk splitting and result writing around parallel data processing as serial work.
What you can do after
Apply serial work with std::experimental::flex_barrier and keep its participant count aligned with arriving threads.
How it connects
Connects serial regions to barriers, parallel regions, and Amdahl’s law.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of C++ Concurrency in Action, Second Edition. The section links point to the parts of the book it draws on.