Информация об изменениях

Сообщение COW and thread safety in Qt от 10.10.2023 9:39

Изменено 10.10.2023 9:39 Skorodum

COW and thread safety in Qt
Тут https://rsdn.org/forum/flame.comp/8614843.1
Автор: andyp
Дата: 09.10.23
по потоко-безапасности и копировании-при-записи (COW or implicit sharing) в Qt.

Что ты под копированием данных для объектов с COW имеешь в виду? Звучит так же мутно, как и qtшная документация Делается ли deep или shallow copy и в каком потоке это делается, если emit сигнала и вызов слота происходят в разных потоках? Есть ли гарантия, что это делается единообразно во всех релизах Qt начиная с N.K? Я не нашел внятного ответа на этот вопрос в свое время и всегда детачил копию контейнера перед тем как ее в сигнал засовывать. Ну это пока еще использовал эти контейнеры вообще.


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

  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
cow qt thread
COW and thread safety in Qt
Тут коллега задает вопросы
Автор: andyp
Дата: 09.10.23
по потоко-безапасности и копировании-при-записи (COW or implicit sharing) в Qt.

Что ты под копированием данных для объектов с COW имеешь в виду? Звучит так же мутно, как и qtшная документация Делается ли deep или shallow copy и в каком потоке это делается, если emit сигнала и вызов слота происходят в разных потоках? Есть ли гарантия, что это делается единообразно во всех релизах Qt начиная с N.K? Я не нашел внятного ответа на этот вопрос в свое время и всегда детачил копию контейнера перед тем как ее в сигнал засовывать. Ну это пока еще использовал эти контейнеры вообще.


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

  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
thread qt cow