COW and thread safety in Qt
От: Skorodum Россия  
Дата: 10.10.23 09:39
Оценка: 12 (2)
Тут коллега задает вопросы
Автор: andyp
Дата: 09.10.23
по потоко-безапасности и копировании-при-записи (COW or implicit sharing) в Qt.

TLDR: Для хрестоматийного использования Qt (когда в одном потоке есть источник данных (сеть/диск/и т.д.) и какая-то обработка в другом потоке, например отображение) можно обойтись без явной синхронизации данных и пользоваться бонусами COW.

Что ты под копированием данных для объектов с COW имеешь в виду?

Документация отвечат очень хорошо:

A deep copy implies duplicating an object. A shallow copy is a reference copy, i.e. just a pointer to a shared data block. Making a deep copy can be expensive in terms of memory and CPU. Making a shallow copy is very fast, because it only involves setting a pointer and incrementing the reference count.
Object assignment (with operator=()) for implicitly shared objects is implemented using shallow copies.


Делается ли deep или shallow copy и в каком потоке это делается, если emit сигнала и вызов слота происходят в разных потоках?

Тут надо отделить копирование от потока выполнения кода (слота).

Есть ли гарантия, что это делается единообразно во всех релизах Qt начиная с N.K?

Этот механизм работает с Qt 4, т.е. 20 лет.

Я постарался сделать минимальный пример который демострирует поведение Qt классов с COW при использовании в разных потоках.


  CMakeLists.txt
cmake_minimum_required(VERSION 3.14)

project(cow LANGUAGES CXX)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core)

add_executable(cow
    main.cpp
)
target_link_libraries(cow Qt${QT_VERSION_MAJOR}::Core)

  main.cpp
#include <QtCore/QCoreApplication>
#include <QtCore/QDebug>
#include <QtCore/QScopedPointer>
#include <QtCore/QThread>
#include <QtCore/QTimer>

#define PRINT_FUNCTION_INFO qDebug() << Q_FUNC_INFO << this << QThread::currentThread();

struct Foo {
    Foo()                       { PRINT_FUNCTION_INFO }
    Foo(const Foo&)             { PRINT_FUNCTION_INFO }
    Foo(Foo&&)                  { PRINT_FUNCTION_INFO }
    Foo& operator=(const Foo&)  { PRINT_FUNCTION_INFO return *this; }
    Foo& operator=(Foo&&)       { PRINT_FUNCTION_INFO return *this; }
    ~Foo()                      { PRINT_FUNCTION_INFO }
};

class Bar : public QObject {
    Q_OBJECT

public:
    explicit Bar(QObject* parent = nullptr) : QObject(parent) {}
    void emitSignals() { emit dataByValueSignal(foos); }
    QVector<Foo> foos;

signals:
    void dataByValueSignal(QVector<Foo>);

public slots:
    void dataByValue(QVector<Foo> foo)
    {
        // It is ok to pass "big" QVector object by value because of COW (but const reference is still better).
        // Also can safely modify copy of 'COW class' transfered with signals and slots in another thread.
        // Using non-const methods will make deep copy of QVector content behind the scene.
        foo.append(Foo()); // <- comment out to see effect of COW
        qDebug() << Q_FUNC_INFO << foo.size();
    }
};

int main(int argc, char *argv[])
{
    // main thread
    const Bar bar;

    // this code will run in another thread
    auto producer = [&bar] {
        Bar anotherBar;
        anotherBar.foos.append(Foo());

        qRegisterMetaType<QVector<Foo>>("QVector<Foo>");
        // connect to object in another thread and pass object with COW by value
        // no copies of QVector *content* at this point and thread safe
        QObject::connect(&anotherBar, &Bar::dataByValueSignal, &bar, &Bar::dataByValue);
        anotherBar.emitSignals();

        // can safely modify local copy of Qt class
        QThread::sleep(1);
        anotherBar.foos.pop_front();
        qDebug() << QThread::currentThread() << anotherBar.foos.size();
    };

    const QScopedPointer<QThread> thread(QThread::create(producer));
    const QCoreApplication a(argc, argv);
    QObject::connect(thread.data(), &QThread::finished, qApp, &QCoreApplication::quit);
    QTimer::singleShot(0, qApp, [&thread]{thread->start();}); // start thread when event loop is running
    return qApp->exec();
}

#include "main.moc"


Ссылки по теме:
implicit sharing
Threads and Implicitly Shared Classes
Implicit sharing iterator problem
Отредактировано 10.10.2023 11:02 Skorodum . Предыдущая версия . Еще …
Отредактировано 10.10.2023 9:39 Skorodum . Предыдущая версия .
thread qt cow
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.