4

I've read through the documentation of std::generator<T> and found no mention of thread-safety.

Now essentially I have a co-routine that generates work elements and I want to consume them with multiple threads. Generating a new work item takes a bit of time, but processing them even longer, hence the co-routine + worker pool approach.

Essentially I'll end up with code like this

auto generator = get_generator();

std::vector<std::thread> threads;

for(int i = 0; i < NUM_THREADS; ++i) {
    threads.emplace_back(worker, generator);
}

And then a worker function without any additional synchronization like this:

void worker(std::generator<...>& generator) {
    for(const auto& element : generator) {
        // processing goes here
    }
}

I'm specifically looking to know if the standard has anything to say about this. And on top if any compiler implementations are thread-safe or not.
After all I'm not writing portable code at the moment, so only some compilers making this thread safe is fine, but for the sake of the internet and future me, knowing what the standard has to say about this would be much more interesting.

Finally if this is not thread safe, how do I make it thread safe the most straightforward way?

8
  • 3
    related: stackoverflow.com/questions/75437328/… Commented Feb 10 at 23:52
  • 5
    "1 I've read through the documentation of std::generator<T> and found no mention of thread-safety." - If there's no explicit mention of thread safety, that usually means that there is none and you have to provide/ensure that yourself Commented Feb 11 at 0:51
  • threads.emplace_back(worker, generator); won't compile because std::thread copies its arguments and std::generator is not copyable. Commented Feb 11 at 4:04
  • Secondly, for(const auto& element : generator) implicitly calls generator.begin(), which can only be called once for each generator object. (It's undefined behavior otherwise.) Commented Feb 11 at 4:16
  • 3
    @MSalters, Re, "having two objects of the same type on two different threads is perfectly safe." That might be true for many C++ classes, but it's misleading to suggest that it always is true. It would be trivially easy to write a C++ class whose behavior was undefined if different instances of the class were concurrently used, each in a different thread from the others. All you'd need is for the different instances to communicate with each other "behind the scenes" (e.g., through private, static members) without synchronization. Commented Feb 13 at 17:44

1 Answer 1

0

Indeed they are not thread safe. But with a little helper class they can be made thread safe.

I'm sure you can also implement your own generator class that has the synchronization built in, but the following has worked for me just fine:

#include <generator>
#include <mutex>
#include <type_traits>


template <typename T>
class sync_generator {
private:
    using generator_iterator = decltype(std::declval<std::generator<T>>().begin());
    using generator_iterator_sentinel = decltype(std::declval<std::generator<T>>().end());

    std::generator<T> generator;
    generator_iterator it;
    generator_iterator_sentinel end;
    std::mutex mutex;

public:
    // Construct by moving in a generator.
    explicit sync_generator(std::generator<T>&& gen);

    sync_generator(const sync_generator<T>& other) = delete;
    sync_generator(sync_generator<T>&& other) = default;

    sync_generator& operator=(const sync_generator<T>& other) = delete;
    sync_generator& operator=(sync_generator<T>&& other) = default;

    // Returns the next value, or std::nullopt if finished.
    std::optional<T> next();
};

template <typename T>
sync_generator<T>::sync_generator(std::generator<T>&& generator)
    : generator(std::move(generator)), it(this->generator.begin()), end(this->generator.end()) {}

template <typename T>
std::optional<T> sync_generator<T>::next() {
    std::lock_guard<std::mutex> lock(mutex);

    if (it == end) return std::nullopt;

    T value = *it;
    ++it;
    
    return value;
}
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.