Тут
коллега задает вопросыАвтор: 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 сигнала и вызов слота происходят в разных потоках?
Тут надо отделить копирование от потока выполнения кода (слота).
* Копирование происходит "in all member functions that change the internal data", т.е. во всех не const методах. В каком потоке это происходит не важно.
* Поток, в котором выполняется слот, зависит от типа соединения, по умолчанию "The slot is executed in the receiver's thread". Подробнее тут.
Есть ли гарантия, что это делается единообразно во всех релизах 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