1

我是 QThread 和多线程的新手,所以我不确定我是否做得正确。该程序到目前为止还没有崩溃,但我想检查一下我是否正确地执行了它。我有一些代码如下(MyThreadClass 继承自 QThread):

std::vector<MyThreadClass* > workThreads;
for(int i=0;i<Solutions.size();i++)
{
    workThreads.push_back(new MyThreadClass(Solutions[i]));
}
for(int i=0;i<workThreads.size();i++)
{
    connect(workThreads[i], SIGNAL(finished()), this, SLOT(onFinished()));
    workThreads[i]->start();
}

bool finished = false;
while(!finished)
{
    if(m_finishedThread==workThreads.size())
        finished=true;

    this->msleep(10);
}

onFinished 函数给出如下:

void MyClass::onFinished()
{
    ++m_finishedThread;
}

因此您可以看到 while 循环正在等待所有线程完成并更新 m_finishedThread 变量。这是一种安全的方法吗?如果所有线程都完成了他们的工作并试图“连接”到 onFinished() 函数,它会导致问题吗?

4

5 回答 5

4

那是安全的。诀窍是您将在 QThread 子类之间建立的连接this将是一个排队连接。这是因为发出信号finished(*) 的线程与所在的线程this(接收者)不同。

排队连接是通过事件发布实现的。一个特殊事件被发布到线程this所在的事件队列中;该事件的处理正在调用您的插槽。因此,您的插槽将只能被访问

  1. 依次
  2. 仅来自一个线程:一个this住在

这显然是线程安全的。

顺便问一下,那是你的真实代码吗?你可以做同样的事情

for (int i = 0; i < numThreads; ++i) 
    threads[i]->wait();

(*) 我说的是发出信号的线程而不是发出信号的对象的亲和性。特别是,您的 QThread 子类对象很可能与this!

问题在哪里?发出的线程finished不是那些对象(和this)所在的线程——是那些对象管理的线程。


附录(2)是实现信号发射的代码。如您所见,检查是针对当前运行的线程与接收者所在的线程进行的。发件人所在的线程没有被考虑在内,因此您不需要thread->moveToThread(thread)做诸如建立排队连接之类的事情。

当前运行的线程怎么可能不是发送者所在的同一个线程?因为这正是 QThread 所做的:QThread 对象存在于一个线程中,它管理的线程(并且发出finished()!)是另一个线程:

// runs in the MANAGED thread; lives in any thread
void QThread::run_in_another_thread() {
    run(); // user-supplied implementation
    emit finished();
}

附录的附录:但是,您是否依赖于finished从另一个线程发出的无证知识?那不是依赖于实现吗?

答案是否定的。只有一个其他选择:finished()作为收获/清理处理的一部分发出,来自 QThread 对象正在离开的事件循环。也就是说,同一个线程this生活在其中。

它不能来自其他任何地方——如果我们正在运行用户代码,我们就不会运行其他任何东西(请记住,一个线程不能运行两种不同的东西)。

这意味着:

  1. 你在线程中有一个正在运行的事件循环this。无论如何你都需要这个。
  2. 调用将是“直接”的(即直接调用),因为发出信号的线程是同一个线程this。所以,我们只是在同一个线程中调用一个函数;根据定义,这是线程安全的。

因此,这完全不会改变我们处理此问题的方式。它需要一个活动的事件循环this,就是这样。


附录:我应该更好地阅读代码。这不会改变我上面所说的,但是:

bool finished = false;
while(!finished)
{
    if(m_finishedThread==workThreads.size())
        finished=true;

    this->msleep(10);
}

这里的循环永远不会返回到事件循环。这意味着您的插槽将永远不会被调用,因为元调用事件将永远不会被处理。请使用 aQTimer或潜入一些调用来QCoreApplication::processEvents代替这种循环!

于 2013-09-23T07:39:04.077 回答
1

我可能不会运行 while(!finished) 循环,因为这会挂起你的主线程(或 while 循环的执行线程)。而是在 onFinished() 中检查正在运行的线程,并在一切完成时发出信号。

如果您需要等待所有线程完成,仍然存在 wait()-Condition。

要修改变量,您可以使用带有信号的 Qt:QueuedConnection 来修改不能直接访问的变量。

小心直接从多个线程访问变量。从不同的线程访问同一个变量时,使用 QMutex 通常是个好主意。

于 2013-09-23T09:38:37.900 回答
1

首先,您不应该QThread直接子类化。见: https ://www.qt.io/blog/2010/06/17/youre-doing-it-wrong

改为创建一个普通类(子类QObject或类似类)并在那里实现您的代码。然后使用moveToThread()将子类移动到该线程。

使用 a 将信号连接到对象中的槽是安全的Qt::QueuedConnection,只要传递的参数不是对象并且您没有在没有任何同步机制的情况下调用这些对象的方法。

无论如何,您想要做的可能更容易使用QtConcurrent.

于 2013-09-23T07:36:49.997 回答
0

乍一看,我会说不。实际上,您已经连接了您的线程finished()信号,而没有指定 connect 的最后一个参数(即ConnectionType

从文档中可以看出,如果不指定此参数,Qt::AutoConnection则为默认参数,并指定如果信号来自同一线程中的对象,或来自不同线程中的对象,程序将表现不同。

在这种情况下,Qt::QueuedConnection将用于这些调用,这意味着当信号将触发对插槽的调用时,此调用将在接收线程的事件循环中排队。如果多个线程同时完成,调用onFinished()将被排队,并被一一调用。

编辑:此外,我建议您考虑基于线程的不同设计方法,并阅读内容,其中说您应该创建一个工作对象,然后将其移动到线程。

于 2013-09-23T07:35:22.910 回答
-2

nop,看起来不错,Qt 的信号/插槽系统是线程安全的,实际上,当发出信号时,它被放在队列的末尾,并由主事件循环按该顺序处理。

于 2013-09-23T07:33:08.573 回答