2016-09-25 51 views
-2

我在我的QT程序中使用多线程。我需要将数据传递给来自主gui线程的工作线程中的worker对象。我在QObject子类中创建了一个setData函数来传递主要gui线程中的所有必要数据。但是我通过查看setData函数中的QThread :: currentThreadId()来验证函数是从主线程调用的。即使worker对象函数是从主线程调用的,这是否确保工作线程仍然拥有自己的数据副本(这是重入类所需的)?请记住,这是在工作线程启动之前发生的。QT多线程数据从主线程传递给工作线程

此外,如果在没有动态内存的类中使用基本数据类型,并且只要其所有其他成员数据都是可重入的,那么没有静态全局变量就是该类可重入的? (它有折返数据成员一样将QString,qlists等加上基本整数布尔变量等)

感谢您的帮助

编辑新的内容:

我的主要问题是根本适合从主gui线程中调用另一个线程中的QObject子类方法,以便将我的数据传递给工作线程以进行工作(在我的情况下,包含备份作业信息的自定义类用于长待处理文件扫描和数据备份)。数据传递全部发生在线程启动之前,所以两个线程都没有同时修改数据的危险(我认为但我不是多线程专家......)听起来好像从你的文章中这样做的方式是使用从主线程到工作线程中的插槽传递数据的信号。我已经确认我的数据备份作业是可重入的,所以我需要做的就是确保工作线程在它们自己的这些类的实例上工作。此外,通过调用QObject子类方法完成的数据传输在工作线程启动之前完成 - 是否可以防止竞争条件并且安全吗?

此外,根据“访问其他线程的QObject的子类”一节here它看起来有点危险在QObject的子类使用插槽...

确定这里是我最近忙的代码... 编辑随着代码:

void Replicator::advancedAllBackup() 
{ 
    updateStatus("<font color = \"green\">Starting All Advanced Backups</font>"); 
    startBackup(); 

    worker = new Worker; 
    worker->moveToThread(workerThread); 
    setupWorker(normal); 

    QList<BackupJob> jobList; 
    for (int backupCount = 0; backupCount < advancedJobs.size(); backupCount++) 
     jobList << advancedJobs[backupCount]; 

    worker->setData(jobList); 
    workerThread->start(); 
} 

的STARTBACKUP功能设置一些布尔和更新GUI。设置工作程序函数连接工作线程和工作对象的所有信号和插槽。 setData函数将工作器作业列表数据设置为后端数据,并在线程启动之前调用,因此没有并发性。 然后我们开始线程并完成它的工作。

而这里的工人代码:

void setData(QList<BackupJob> jobs) { this->jobs = jobs; } 

所以我的问题是:这是安全的?

+0

“通过调用QObject子类方法完成的数据传输是在工作线程启动之前完成的 - 这是否会阻止竞争条件并且安全?”这里的'QObject'没有关系。重要的是,如果实施你的方法,实际上你的整个班级都是安全的。您如何期望我们能够决定在没有您提供任何代码供我们谈论的情况下? –

+0

“在QObject子类中使用插槽看起来有点危险......“这是一个无用的观察(你实际上不能使用它),一个特定的方法,无论是否是槽,都是线程安全的或不是线程安全的,如果它不是线程安全的,那么它只能从你应该声明它是这样调用的:在这些方法的开头添加'Q_ASSERT(QThead :: currentThread()== thread());'可以从任何线程调用线程安全的方法。一个线程不安全的方法安全地调用发送一个信号并将该方法连接到它 –

回答

1

为了使用Qt的机制在队列之间传递数据,你不能直接调用对象的函数。您需要可以使用signal/slot mechanism,或者您可以使用QMetaObject::invokeMethod电话:

QMetaObject::invokeMethod(myObject, "mySlotFunction", 
          Qt::QueuedConnection, 
          Q_ARG(int, 42)); 

如果发送方和接收对象有正在运行的事件队列,这只会工作 - 即主要的或QThread的基于线程。

对于你的问题的另一部分,请参阅重入Qt的文档部分: http://doc.qt.io/qt-4.8/threads-reentrancy.html#reentrant

许多Qt类是可重入的,但因为使他们线程安全的,他们不是线程安全的, 会导致额外的开销 反复锁定和解锁QMutex。例如,QString是 可重入,但不是线程安全的。您可以同时安全地从多个线程安全地访问QString的不同 实例,但是您的 无法同时安全地从多个线程 中安全地访问QString的同一实例(除非您使用QMutex自行保护访问)。

+1

1.'QObject'不运行事件队列,事件队列是每个线程而不是每个对象。没关系,你可以从任何线程发出一个信号,包括原生的原生线程。执行'main'的线程并不是以'QThread'开始的:)接收线程需要一个正在运行的事件循环来处理事件,但不一定需要使用'QThread'开始,你可以使用'std :: thread'来运行一个事件循环;一个隐藏的实例一旦你开始循环,'QThread'就会被Qt自动构建。 –

2

你的问题有一些误解。

重入和多线程是正交的概念。单线程代码很容易被迫应付重入 - 并且一旦你重新进入事件循环(因此你不应该)。

您问的问题与更正,因此:如果数据成员支持多线程访问,类的方法是线程安全的吗?答案是肯定的。但这是一个最没用的答案,因为你错误地认为你使用的数据类型支持这种访问。他们最有可能不要

实际上,除非明确地找出它们,否则不太可能使用多线程安全的数据类型。 POD类型不是,大多数C++标准类型都不是,大多数Qt类型都不是。只是为了没有误解:QString不是多线程安全的数据类型!下面的代码是未定义行为(它会崩溃,烧伤和发送电子邮件到你的配偶,似乎是从非法情人):

QString str{"Foo"}; 
for (int i = 0; i < 1000; ++i) 
    QtConcurrent::run([&]{ str.append("bar"); }); 

的后续问题可能是:

  • 我的数据成员是否支持多线程访问?我以为他们做到了。

    不,他们不是,除非你显示的代码证明,否则。

  • 我甚至需要支持多线程访问吗?

    也许吧。但避免完全需要它更容易。

与Qt类型有关的混淆的可能来源是它们的隐式共享语义。值得庆幸的是,他们对多线程的关系是相当简单的表达:

  1. Qt的隐含共享类的任何实例可以从任何一个线程在给定时间访问。推论:每个线程需要一个实例。复制你的对象,并在每个副本中使用它自己的线程 - 这是非常安全的。这些实例最初可能共享数据,并且Qt将确保任何copy-on-writes都可以为您安全地完成线程。

  2. 边栏:如果使用迭代器或内部指针指向非常量实例上的数据,则必须在构造迭代器/指针之前强制对象detach()迭代器的问题在于,当对象的数据分离时它们会失效,并且可能在实例为非const的任何线程中发生分离 - 因此至少有一个线程最终会导致无效的迭代器。我不会再讨论这个问题,因为隐含共享的数据类型安全地实现和使用是非常棘手的。使用C++ 11,不再需要隐式共享:它们是C++ 98中缺少移动语义的解决方法。

那是什么意思呢?这意味着这样的:

// Unsafe: str1 potentially accessed from two threads at once 
QString str1{"foo"}; 
QtConcurrent::run([&]{ str1.apppend("bar"); }); 
str1.append("baz"); 

// Safe: each instance is accessed from one thread only 
QString str1{"foo"}; 
QString str2{str1}; 
QtConcurrent::run([&]{ str1.apppend("bar"); }); 
str2.append("baz"); 

原代码可以因此被固定:

QString str{"Foo"}; 
for (int i = 0; i < 1000; ++i) 
    QtConcurrent::run([=]() mutable { str.append("bar"); }); 

这并不是说,该代码是非常有用的:当所述函子内破坏修改的数据丢失工作者线程。但它用来说明如何处理Qt值类型和多线程。这就是它的工作原理:构造仿函数的每个实例时将获取str的副本。然后这个仿函数被传递给一个工作线程来执行,在那里它的字符串拷贝被追加到。该副本最初与原始线程中的str实例共享数据,但QString将线程安全地复制数据。你可以明确地写出来的仿函数要清楚发生了什么:

QString str{"Foo"}; 
struct Functor { 
    QString str; 
    Functor(const QString & str) : str{str} {} 
    void operator()() { 
    str.append("bar"); 
    } 
}; 
for (int i = 0; i < 1000; ++i) 
    QtConcurrent::run(Functor(str)); 

我们如何处理与使用Qt类型进出一个工人对象之间传递数据?所有与对象的通信,当它在工作者线程中时,都必须通过信号/插槽完成。 Qt会自动以线程安全的方式为我们复制数据,以便每个实例的值只能在一个线程中访问。例如:

class ImageSource : public QObject { 
    QImage render() { 
    QImage image{...}; 
    QPainter p{image}; 
    ... 
    return image; 
    } 
public: 
    Q_SIGNAL newImage(const QImage & image); 
    void makeImage() { 
    QtConcurrent::run([this]{ 
     emit newImage(render()); 
    }); 
    } 
}; 

int main(int argc, char ** argv) { 
    QApplication app...; 
    ImageSource source; 
    QLabel label; 
    label.show(); 
    connect(source, &ImageSource::newImage, &label, [&](const QImage & img){ 
    label.setPixmap(QPixmap::fromImage(img)); 
    }); 
    source.makeImage(); 
    return app.exec(); 
} 

源信号和标签线程上下文之间的连接是自动的。该信号恰好在默认线程池中的工作线程中发出。在信号发射时,源线和目标线程进行比较,如果不同,仿函数将包装在事件中,事件发布标签,并且标签的QObject::event将运行设置像素映射的仿函数。这是所有线程安全的,并利用Qt使其几乎毫不费力。目标线程上下文&label非常重要:如果没有它,函数将运行在工作线程中,而不是UI线程。

请注意,我们甚至不必将对象移动到工作线程:实际上,应该避免将QObject移动到工作线程,除非对象确实需要对事件作出反应并且不仅仅是生成一块数据。您通常想要移动处理通信的对象或从UI抽象出的复杂应用程序控制器。单纯的数据生成通常可以使用QtConcurrent::run使用信号来抽象出将工作线程数据提取到另一个线程的线程安全魔术。

+0

感谢您的回复。我的回复不适合在这里,所以我张贴在下面。 –