Безопасно ли выполнять co_await и возобновлять сопрограмму в другом потоке без синхронизации?

Cppreference есть этот пример для co_await, который возобновляет сопрограмму в другом потоке.

Но безопасно ли это делать без синхронизации?

В частности, resuming_on_new_thread() начинается с одного потока, затем возобновляется после co_await с другим потоком, обращающимся к тому же «фрейму» сопрограммы. Обычно, если вы получаете доступ к одним и тем же данным из двух потоков, вам нужна синхронизация.

#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>
 
auto switch_to_new_thread(std::jthread& out)
{
    struct awaitable
    {
        std::jthread* p_out;
        bool await_ready() { return false; }
        void await_suspend(std::coroutine_handle<> h)
        {
            std::jthread& out = *p_out;
            if (out.joinable())
                throw std::runtime_error("Output jthread parameter not empty");
            out = std::jthread([h] { h.resume(); });
            // Potential undefined behavior: accessing potentially destroyed *this
            // std::cout << "New thread ID: " << p_out->get_id() << '\n';
            std::cout << "New thread ID: " << out.get_id() << '\n'; // this is OK
        }
        void await_resume() {}
    };
    return awaitable{&out};
}
 
struct task
{
    struct promise_type
    {
        task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() {}
    };
};
 
task resuming_on_new_thread(std::jthread& out)
{
    std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
    co_await switch_to_new_thread(out);
    // awaiter destroyed here
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
}
 
int main()
{
    std::jthread out;
    resuming_on_new_thread(out);
}

Рассмотрим, есть ли у функции данные:

task resuming_on_new_thread(std::jthread& out)
{
    int data = 0; // Store some data using thread A.
    std::cout << "Coroutine started on thread: " << std::this_thread::get_id() << '\n';
    co_await switch_to_new_thread(out);
    // awaiter destroyed here
    std::cout << "Coroutine resumed on thread: " << std::this_thread::get_id() << '\n';
    std::cout << data << '\n'; // Access data with thread B. **Is this safe?**
}

Подумайте о вопросе, который вы задаете. Скажем, как узнать, что сопрограмма закончила работу и возобновить ее в другом потоке? Если вы это знаете, значит, вы уже выполнили синхронизацию — неявно через какие-то другие классы. Правильный вопрос: «Как гарантировать, что разные потоки не будут одновременно обращаться к сопрограмме, вызывая гонку данных?»

ALX23z 13.02.2023 17:20
Запуск PHP на IIS без использования программы установки веб-платформы
Запуск PHP на IIS без использования программы установки веб-платформы
Установщик веб-платформы, предлагаемый компанией Microsoft, перестанет работать 31 декабря 2022 года. Его закрытие привело к тому, что мы не можем...
Оптимизация React Context шаг за шагом в 4 примерах
Оптимизация React Context шаг за шагом в 4 примерах
При использовании компонентов React в сочетании с Context вы можете оптимизировать рендеринг, обернув ваш компонент React в React.memo сразу после...
Библиотека для работы с мороженым
Библиотека для работы с мороженым
Лично я попрощался с операторами print() в python. Без шуток.
Настройка шаблона Metronic с помощью Webpack и Gulp
Настройка шаблона Metronic с помощью Webpack и Gulp
Я пишу эту статью, чтобы поделиться тем, как настроить макет Metronic с помощью Sass, поскольку Metronic предоставляет так много документации, и они...
Уроки CSS 6
Уроки CSS 6
Здравствуйте дорогие читатели, я Ферди Сефа Дюзгюн, сегодня мы продолжим с вами уроки css. Сегодня мы снова продолжим с так называемых классов.
Что такое Css? Для чего он используется?
Что такое Css? Для чего он используется?
CSS, или "Каскадные таблицы стилей", - это язык стилей, используемый в веб-страницах. CSS является одним из основных инструментов веб-разработки...
0
1
70
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

Да, это безопасно. Конструктор std::jthread — это синхронизация.

Имеет смысл. Конструктор jthread вызывается в строке out = std::jthread([h] { h.resume(); }); и отвечает за синхронизацию.

trev 15.02.2023 12:08

Это безопасно. Помните, что когда сопрограмма приостанавливается, она возвращает управление вызывающей стороне. Таким образом, только поток, который возобновляет сопрограмму, может продолжить выполнение тела сопрограммы. В вашем примере сопрограмма запускается в основном потоке, затем приостанавливается, затем возобновляется в out.

Гонка данных произойдет только в том случае, если вы попытаетесь возобновить одну и ту же сопрограмму из двух разных потоков, что является неопределенным поведением, как указано в примечаниях std::coroutine_handle<Promise>::resume() здесь

Поведение не определено, если *this не относится к приостановленной сопрограмме [...]. Одновременное возобновление сопрограммы может привести к гонке данных.

Другие вопросы по теме