QThread:从不同线程修改变量的安全方法?

时间:2013-09-23 07:20:28

标签: c++ multithreading qt qthread

我是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()函数,它会导致问题吗?

5 个答案:

答案 0 :(得分:4)

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

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

  1. 顺序
  2. 仅来自一个帖子:this居住在
  3. 这显然是线程安全的。

    顺便问一下,那是你真正的代码吗?您可以使用

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

    (*)我在谈论发出信号的线程,而不是发出信号的对象的亲和力。特别是,你的QThread子类对象可能存在于与this完全相同的线程中!

    捕获的地方在哪里?发出finished的线程不是那些对象(和this)所居住的线程 - 是这些对象的托管线程。


    附录(2)this是实现信号发射的代码。如您所见,检查是针对当前正在运行的线程接收器所在的线程。发件人所在的主题不会被考虑在内,因此您需要执行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();
    }
    

    Addendun to the addendour :但是,您是否依赖于finished从另一个线程发出的无证知识?这不是依赖于实施吗?

    答案是。只有另外一个选择:finished()作为收割/清理处理的一部分被发出,来自QThread对象离开的事件循环。也就是说,同一个帖子this正在生活。

    它不能来自其他任何地方 - 如果我们正在运行用户代码,我们没有运行任何其他东西(请记住一个线程无法运行两个< / em>不同的事情)。

    这意味着:

    1. 你在线程this中有一个正在运行的事件循环。无论如何你都需要这个。
    2. 调用将是“直接”(即直接调用),因为发出信号的线程与this生成的线程相同。因此,我们只是在同一个函数中调用函数螺纹;根据定义,这是线程安全的。
    3. 所以,这对我们处理这个问题的方式完全没有任何改变。它需要this中的活动事件循环,就是这样。


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

      bool finished = false;
      while(!finished)
      {
          if(m_finishedThread==workThreads.size())
              finished=true;
      
          this->msleep(10);
      }
      

      这里的循环永远不会返回到事件循环。这意味着永远不会调用您的插槽,因为永远不会处理metacall事件。请使用QTimer或潜入一些QCoreApplication::processEvents来电,而不是这种循环!

答案 1 :(得分:1)

首先,您不应该直接对QThread进行子类化。看到: http://blog.qt.digia.com/blog/2010/06/17/youre-doing-it-wrong/

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

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

无论如何,使用QtConcurrent可能更容易实现您想要做的事情。

答案 2 :(得分:1)

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

如果你需要等待所有线程完成,那么仍然有wait() - 条件。

对于修改变量,您可以使用带有信号的Qt:QueuedConnection来修改变量(如果它不可直接访问)。

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

答案 3 :(得分:0)

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

从文档中可以看出,如果不指定此参数,Qt::AutoConnection是默认参数,并指定如果信号来自同一线程中的对象,程序的行为会有所不同,或者在不同的主题中。

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

编辑:另外,我建议您根据线程考虑一种不同的设计方法,并阅读this,其中您应该创建一个工作对象,然后将其移动到该线程。

答案 4 :(得分:-2)

nop,似乎没问题,Qt的信号/插槽系统是线程安全的,实际上,当发出信号时,它被放在队列的末尾,并由主事件循环按此顺序处理。