Видимость памяти в параллелизме C ++ (без гонки данных) - PullRequest
2 голосов
/ 20 июня 2019

Это дополнительный вопрос Shared_ptr и видимость памяти в c ++ и Создание объекта в потоке A, использование в потоке B. Требуется мьютекс? .

Этот вопрос больше касается видимости памяти, а не гонки данных.

В Java у меня есть:

ExecutorService executor = Executors.newSingleThreadExecutor();
Integer i = new Integer(5); // no write to i afterwards
executor.submit(() -> {
    System.out.println(i);
});

Я не думаю, что это потокобезопасно.Поскольку нет необходимости помещать значение 5 в основную память, оно может оставаться в кэше ЦП основного потока.Поскольку нет барьера памяти, потоку исполнителя не гарантируется увидеть значение 5. Чтобы убедиться, что значение находится в основной памяти, вы либо используете синхронизацию, либо используете AtomicInteger, либо volatile int.

Если вы делаете что-то подобное с shared_ptr в C ++, это безопасно?

auto sp = std::make_shared<int>(5); // no write to the int afterwards
myExecutor.submit([sp](){
    std::cout << sp;
});

Поток исполнителя гарантированно увидит значение 5?Обратите внимание, что shared_ptr копируется в лямбду, а не в int.

Вот более полный пример:

Предположим, у меня есть основной поток и рабочий поток.В основном потоке я создал shared_ptr<Object> и скопировал shared_ptr в рабочий поток. Безопасно ли использовать копию shared_ptr, если вообще нет синхронизации в классе Object (НЕТ записи в объект?после постройки)?

Моя главная загадка состоит в том, что Объект создается в главном потоке в куче, shared_ptr копируется, но не Объект.Будет ли рабочий поток определенно иметь видимость объекта в памяти?Возможно ли, что значение Object на самом деле находится в кэше ЦП основного потока, а не в основной памяти?

struct WorkingQueue{
    WorkingQueue()=default;

    void push(std::function<void()> task){
        std::lock_guard<std::mutex> lock{mutex};
        queue.push(std::move(task));
    }

    std::optional<std::function<void()>> popIfNotEmpty(){
        std::lock_guard<std::mutex> lock{mutex};
        if(queue.empty()){
            return std::nullopt;
        }
        auto task = queue.front();
        queue.pop();
        return task;
    }

    bool empty(){
        std::lock_guard<std::mutex> lock{mutex};
        return queue.empty();
    }

    mutable std::mutex mutex;
    std::queue<std::function<void()>> queue;
};

int main(){
    WorkingQueue queue;
    std::atomic<bool> stopFlag{false};
    auto f = std::async(std::launch::async, [&queue, &stopFlag](){
        while(!stopFlag || !queue.empty()){
            auto task = queue.popIfNotEmpty();
            if(task){
                (*task)();
            }
        }
    });
    auto sp = std::make_shared<int>(5);
    queue.push([sp](){
        std::cout << *sp;
    });

    stopFlag = true;
    f.get();
}

Гарантирован ли этот программист для вывода 5?

1 Ответ

1 голос
/ 20 июня 2019

безопасно ли использовать копию shared_ptr, если синхронизация в классе объектов вообще отсутствует

Да, std::shared_ptr синхронизируется, так что счетчик ссылок является потокобезопасным,Однако синхронизация чтения / записи объекта, на который он указывает, зависит от вас.

Редактировать после редактирования вопроса:

Гарантируется ли в потоке исполнителя значение 5?

Нет, это то же самое, что передать необработанный указатель на ваш поток myExecutor.

...