c++multithreadingbarrier

Porting boost::barrier to std::barrier from C++20


I'm trying to better understand how to use the std::barrier in c++20, and for that I'm adapting a sample code which uses boost::barrier in a manner which I can change the minimum as possible in the std::barrier code. The code using boost::barrier is the following:

#include <thread>
#include <mutex>
#include <cstdio>
#include <boost/thread/barrier.hpp>

boost::barrier fist_bump(10); 

unsigned int bags_of_chips = 1; // start with one on the list
std::mutex pencil;

void cpu_work(unsigned long workUnits) {
    unsigned long x = 0;
    for (unsigned long i; i<workUnits*1000000; i++) {
        x++;
    }
}

void barron_shopper() {
    cpu_work(1); // do a bit of work first
    fist_bump.wait();
    std::scoped_lock<std::mutex> lock(pencil);
    bags_of_chips *= 2;
    printf("Barron DOUBLED the bags of chips.\n");
}

void olivia_shopper() {
    cpu_work(1); // do a bit of work first
    {
        std::scoped_lock<std::mutex> lock(pencil);
        bags_of_chips += 3;
    }
    printf("Olivia ADDED 3 bags of chips.\n");
    fist_bump.wait();
}

int main() {
    std::thread shoppers[10];
    for (int i=0; i<10; i+=2) {
        shoppers[i] = std::thread(barron_shopper);
        shoppers[i+1] = std::thread(olivia_shopper);
    }
    for (auto& s : shoppers) {
        s.join();
    }
    printf("We need to buy %u bags_of_chips.\n", bags_of_chips);
}

and it works as expected by always printing the message (at the end of execution):

We need to buy 149 bags_of_chips.

The same code using c++20 std::barrier gets stuck after all the threads execute and don't print the final message "we need to buy..." so I guess it is in a deadlock state with one of the threads waiting to be arrived. The code (as I said, I tried to modify the minimum as possible of code) is the following:

#include <thread>
#include <mutex>
#include <cstdio>
#include <barrier>

unsigned int bags_of_chips = 1; // start with one on the list
using std::mutex, std::barrier, std::scoped_lock, std::thread;
mutex pencil;
void olivia_shopper();
barrier fist_bump(10,olivia_shopper);

void cpu_work(unsigned long workUnits) {
    unsigned long x = 0;
    for (unsigned long i=0; i<workUnits*1000000; i++) {
        x++;
    }
}

void barron_shopper() {
    cpu_work(1); // do a bit of work first
    fist_bump.arrive_and_drop();
    {
        scoped_lock<mutex> lock(pencil);
        bags_of_chips *= 2;
    }
    printf("Barron DOUBLED the bags of chips.\n");
}

void olivia_shopper() {
    cpu_work(1); // do a bit of work first
    {
        scoped_lock<mutex> lock(pencil);
        bags_of_chips += 3;
    }
    printf("Olivia ADDED 3 bags of chips.\n");
    fist_bump.arrive_and_wait();
}


int main() {
    thread shoppers[10];
    for (int i=0; i<10; i+=2) {
        shoppers[i] = thread(barron_shopper);
        shoppers[i+1] = thread(olivia_shopper);
    }
    for (auto& s : shoppers) {
        s.join();
    }
    printf("We need to buy %u bags_of_chips.\n", bags_of_chips);
}

The output is similar to:

Olivia ADDED 3 bags of chips. Barron DOUBLED the bags of chips. Barron DOUBLED the bags of chips. Olivia ADDED 3 bags of chips. Barron DOUBLED the bags of chips. Olivia ADDED 3 bags of chips. Olivia ADDED 3 bags of chips. Barron DOUBLED the bags of chips. Barron DOUBLED the bags of chips. Olivia ADDED 3 bags of chips. Olivia ADDED 3 bags of chips.

and the execution never reaches the end. So I want to know how to use this code correctly since in the documentation there wasn't clear examples.


Solution

  • Okay, there are a few things.

    1. barrier fist_bump(10,olivia_shopper) - This line tells the barrier to run olivia_shopper once all 10 threads have reached the barrier. The boost version doesn't do this. Recommend not passing in olivia_shopper as a completion function.

    2. fist_bump.arrive_and_drop() - This causes it to arrive, but not wait, and reduce the number of threads required to trigger the barrier the next time (which is irrelevant in this example). Recommend use of arrive_and_wait(), which is the behavior of boost::barrier::wait()

    3. Finally, I would expect the value, given the code, to always be 512. The 5 olivia_shopper threads should each increment the value by 3, which with the initial 1, would be 16. Then the remaining barron_shopper threads would double it 5 times, for a total of 512. See https://godbolt.org/z/Kv7ofdsbv