与正在运行的工作线程进行Qt通信

时间:2016-01-15 01:40:20

标签: c++ multithreading qt signals-slots qthread

Qt docs获取示例:一个创建一个控制器和一个worker,将worker移动到特定的线程并启动线程。启动/停止触发通过信号/插槽完成。

现在假设doWork方法取决于某些参数(例如中间结果的输出速率(以某种可视化形式)),并且想要通过用户界面调整该参数,而函数是运行。 如果使用信号/插槽机制(在接口和工作器之间),这将不起作用,因为工作程序的接收槽将在运行事件循环之后排队,因此将在doWork完成后执行。

我提出了以下解决方案:

方法1:

public中创建参数Worker并直接通过界面进行更改。

class Worker : public QObject
{
    Q_Object

public:
    int parameter;
    ...
}

Interface::updateParameter(int p) { worker_instance.parameter = p; }

方法2:

将参数移动到接口本身或一些额外的类(生活在除工作者之外的另一个线程中),并让工作人员在每次需要时加载它。

class Interface : public QWidget
{
    Q_Object

private:
    int parameter;

public:
    int getParameter() { return parameter; }
    ...
}

Worker::doWork() {
    ...
    interface_instance.getParameter();  // load parameter;
    ...
}

不是将参数放在接口中,而是可以将它放在另一个类中,并通过信号/插槽机制与该类进行交互。

关于易用性我绝对更喜欢方法1.它还使事物保持在实际所属的位置。 方法2似乎并没有提供优于方法1的优势。 有什么意见吗?

还有什么不同的方法来处理这样的事情吗?例如,观看动画,我想有人想要启用动画速度的调整,这在某种程度上需要钩子进入动画过程。

您对此主题有任何想法或知识吗?

3 个答案:

答案 0 :(得分:1)

  

如果使用信号/插槽机制(在接口和工作者之间)   这不会起作用,因为工人的接收槽将排队   在运行事件循环之后,因此将在doWork之后执行   结束。

这不是必然的情况,你可以使doWork()无阻塞,你做一部分工作,然后让事件循环循环,允许与其他线程通信,你可以使用排队连接来转移这样的数据,然后做另一个工作步骤,另一个循环等等。这使您可以监视进度,暂停,取消工作以及在此期间来回传输数据。

阻止与非阻塞工作者:

main            main    
|       worker  |       worker
|------>|       |------>|
|       |       |       |
|       |       |<----->
|       |       |       |
|       |       |       |
|<------|       |<----->
|               |       |
|               |<------|
|               |   

Here我已经设置了一个示例,将工作分解为步骤并运行这些步骤,而不会阻止工作线程允许通信通过。概念是相同的 - 将工作拆分为工作单元,跟踪工作对象中的状态,使用带参数的信号和插槽进行控制和数据传输。

请注意不要过度使用排队连接,因为与直接连接相比,它们非常慢,如果您充斥事件循环,实际上会丢失性能并且应用程序可能会挂起。考虑到目标通常是视觉或用户控制反馈,尽量不要每秒发出太多排队连接。像每秒30这样的东西是响应式用户界的最佳选择,但你甚至可以用更少的东西。

答案 1 :(得分:1)

这听起来有点像你想在这里进行线程同步,即互斥和线程锁定/解锁。

因此,在您的代码中,您将声明一个互斥变量(如果您有许多工作者,并且您的参数是特定于工作者的,那么它应该在工作者类中公开存在,否则如果它是“单例”参数,则将其粘贴在main中)。这实际上是一个全局变量。您还需要一个作为参数的全局变量。

然后在你的doWork()函数中:

Worker::doWork() {
    ...
    // Get the parameter value
    g_mutex.lock();
    // take copy of the global parameter (m_ is member and g_ is global)
    m_param = g_param;  
    g_mutex.unlock();
    ...
}

然后,您需要在任何地方实际设置/更改参数:

    // Set the parameter value
    g_mutex.lock();
    g_param = ...;  
    g_mutex.unlock();

这可以确保在您尝试读取参数时不会覆盖/修改参数(如果您感到困扰)。这样做是很好的线程安全的做法。但是在“设置”方面使用互斥锁更重要。

你可以在读取时不使用互斥锁,因为你可能并不真正关心某人同时写入它(如果它的单个指令像POD一样写入),但如果它是一个结构,那么你可能会关心。

另一种方法是重新编写代码,以便你的工作单位更小(我认为像@ddriver那样......似乎是个好主意。

答案 2 :(得分:0)

这取决于你的工作的性质。它是否会消耗100%的CPU,或者它是基于事件的,是由定时事件引发的?你提到了可视化,这意味着它更像是一个定时事件,你唤醒你的线程,处理/更新数据,然后睡觉,直到它呈现下一帧为止。

如果是这种情况,您确实可以使用信号槽来更新数据。为此,您不要实现run(),因此使用默认实现(使用自己的事件循环)。在start()函数中,然后排队QTimer事件以调用doWork(),并调用父start()。事件循环启动,调用你的doWork(),你处理你的数据(按照你自己的步调 - 这不是GUI线程,你没有阻止任何东西),并在doWork结束时再次排队另一个QTimer事件,并返回到事件循环。

由于它正在运行事件循环,因此在这种情况下所有信号/插槽处理都将起作用。更重要的是,排队的连接信号/插槽根据定义是同步的,这意味着您不需要同步数据访问。