【问题标题】:Qt - synchronizing threads not working - threads stop but really don't, and sometimes do when they shouldn'tQt - 同步线程不起作用 - 线程停止但实际上没有,有时在不应该的时候做
【发布时间】:2015-06-11 06:45:31
【问题描述】:

我有四个线程。两个使用 OpenCV 播放和分析视频,一个保存数据并充当视频和 GUI 之间的中间人,一个是 GUI。

我的想法是这样的 - 视频线程做事,将帧发送给中间人,然后自行停止。如果它是发送的对中的第一帧,中间人只会设置一个标志 - otherFrame。理论上,由于视频一个​​停止​​了,现在另一个视频发送一个帧,另一个帧的标志是 True,所以中间人将两个帧都发送到 onPassFramesGui,帧被放置在 QLabels 中,然后两个线程都恢复了。然后它循环。

这就是理论,我毫不怀疑它是错误的。实际上,分辨率较低的视频迟早会超过另一个。就像它从未停止过一样。

在视频线程中,虽然它们做的事情略有不同,但这是代码的共同部分:

for(nrKlatkiOne=1; nrKlatkiOne<maxFramesOne; nrKlatkiOne++) {
(....)
    emit sendFrameOne(imageOne, nrKlatkiOne);
    pauseThread();
    pause.lock(); //mutex
    if(threadPausedOne==true){
        pausedCond.wait(&pause); //QWaitCondition
    }
    pause.unlock();
}

void VideoOne::pauseThread() { //in VideoTwo "One"s are replaced with "Two"s, so the variables are exclusive to their threads
    if(threadPausedOne==false){
        pause.lock();
        threadPausedOne=true;
        pause.unlock();
     }
}

void VideoOne::resumeThread() {
    if(threadPausedOne==true){
        pause.lock();
        threadPausedOne=false;
        pause.unlock();
        pausedCond.wakeAll();
    }
}

在中间人线程中,这是接收信号的槽(VideoTwo 也有类似的信号):

void Analiza::onSendFrameOne(QImage frameOneImage, int frameNoOne) {  

//videoOne->pauseThread();        
one = frameOneImage;    
frOne = frameNoOne;  

if(otherFrameTwo == true && videoTwoLoaded == true){  
    emit passFramesGui(one, two, frOne, frTwo);  
    pauseMutex.lock();     
    otherFrameOne = false;    
    otherFrameTwo = false;
    pauseMutex.unlock();
} else if(videoTwoLoaded == false) {   
    emit passFramesGui(one, two, frOne, frTwo);   //to GUI
}
if(otherFrameTwo == false){     
    pauseMutex.lock();
    otherFrameOne = true;                  
    pauseMutex.unlock();
    qDebug() << "otherFrameOne = true";
}
}

最后但并非最不重要的一点,请耐心等待,此功能在 GUI 中设置图像

void MainWindow::onPassFramesGui(QImage one, QImage two, int frameNoOne, int frameNoTwo){ 

if(videoOneLoaded==true) {          
    <converting image>
    <keeping aspect ratio>
    (setting stuff)
}

if(videoTwoLoaded==true) { 
    <same stuff here>
}

if(videoOneLoaded == true) { 
    videoOne->resumeThread();
}

if(videoTwoLoaded == true) { 
    videoTwo->resumeThread();  
}
}

我已经放了 QDebug 消息来大致了解发生了什么。迟早会发生这种情况:

(....)
otherFrameOne == true && videoOneLoaded == true //frames emitted from onSendFramesTwo
otherFrameTwo = true //onSendFramesTwo received the frame, waits
otherFrameTwo == true && videoTwoLoaded == true //onSendrameOne sent the frames
otherFrameOne = true //onSendFrameOne received a frame
otherFrameOne = true //onSendFrameOne received another frame? But it's stopped...

我很确定停止线程工作。如果我从中删除 resumeThread() ,程序将停止。它将发送帧 1、2 -> gui,然后是 1,然后它就会停止。

另一种情况是只加载了一个视频,而不是两个。视频线程将在不恢复时随机停止。我有一个 qDebugs 消息的信号,在视频停止后,它不会做任何事情(可以在我更改选项卡时恢复 - 这会导致 threadPausedOne/Two 设置为 true,然后设置为 false。

代码编译没有任何错误或警告。

这样做的正确方法是什么?这是我第一次做这种事情。

哦,一个额外的问题。拥有两个稍微不同的独立对象更好,还是一个拥有 60% 以上的代码并只创建多个实例?

【问题讨论】:

  • 为什么不为 VideoOne 暂停\恢复机制使用另一个条件变量? threadPausedOne 被多个线程访问,没有互斥(if(...))。如果你想要一个暂停机制,为什么不简单地使用信号和槽呢?
  • @UmNyobe 你到底是什么意思?
  • 现在没有时间分析您的代码,但是您在锁定暂停互斥体之前检查了 threadPausedOne,所以我想说,这是您的错误。
  • 显然你不需要布尔值threadPausedOne。可以无条件使用条件变量pausedCond
  • @UmNyobe 在代码中看起来如何?为了让病情入睡,我必须打电话给一些东西。我见过的每个例子都使用了布尔值。 PS:每个 threadPausedNumber 仅由一个线程使用。嗯。我会尝试信号和插槽。有些事情我没有想到。

标签: c++ multithreading qt


【解决方案1】:

你把它复杂化了,引入了很多标志和同步对象。我建议您在每个视频线程中使用两个 QSemaphore。我将它们命名为allowWorkhasFrame

视频线程的工作方式如下:

// Wait until middleman allows work (when it has handled a pair of frames)
allowWork.acquire(1);

// Do frame processing here
// ...

// Tell middleman one frame is ready
hasFrame.release(1);

中间人线程的工作方式如下:

// Wait until both video threads have a frame
video1.hasFrame.acquire(1);
video2.hasFrame.acquire(1);

// Handle a pair of frames here
// ...

// Tell video threads to make another pair of frames
video1.allowWork.release(1);
video2.allowWork.release(1);

在启动代码中,您会将allowWork 初始化为1,将hasFrame 初始化为0。

使用这种方法,还可以轻松地为来自 video1 和 video2 的帧添加缓冲区(例如,两个视频线程都在工作,直到它们各自准备好 16 帧,而中间人仍然在一对可用时处理),这种方式您可能会获得更好的线程利用率。

【讨论】:

  • 我仍然需要加载视频的标志,因为可能只有一个加载,而不是两个。但是谢谢你的回答——在这个问题上这肯定是一个新的 PoV。我会尽快尝试
  • 有机会应用此解决方案...GUI 变得无响应,应用程序每十几或两帧冻结并恢复,而且视频无论如何都不同步...我一定做错了什么跨度>
  • 可能。根据我的设计,你的 GUI 线程没有任何锁,所以它不应该冻结。
  • 我的也不行。 GUI 线程不会做任何与直接操作一个按钮或另一个标签无关的事情。
  • 我只能建议你仔细调试。你所描述的不应该发生在我的设计中,没有代码,很难猜测。我相信您不小心从锁定的线程或类似的东西中调用了某些方法。无论如何,最好花时间调试它。
【解决方案2】:

每当您使用共享变量时,您必须锁定您的互斥锁,用于写入(如您所做的那样),也用于读取

例子:

void VideoOne::pauseThread() { //in VideoTwo "One"s are replaced with "Two"s, so the variables are exclusive to their threads
    pause.lock(); // lock here
    if(threadPausedOne==false){
        // pause.lock(); // not here!
        threadPausedOne=true;
        // pause.unlock(); // not here!
    }
    pause.lock(); // unlock here
}

void VideoOne::resumeThread() {
    pause.lock(); // lock here
    if(threadPausedOne==true){
        // pause.lock(); // not here!
        threadPausedOne=false;
        // pause.unlock(); // not here!
        pausedCond.wakeAll();
    }
    pause.lock(); // unlock here
}

对于resumeThread(),如果必须在pause 未锁定时调用wakeAll,则执行此操作。这是您可能不得不在其他地方使用以防止死锁的技巧...

void VideoOne::resumeThread() {
    bool needWakeAll = false;
    pause.lock(); // lock here
    if(threadPausedOne==true){
        threadPausedOne=false;
        needWakeAll=true;
    }
    pause.lock(); // unlock here
    if ( needWakeAll )
        pausedCond.wakeAll();
}

onSendFrameOne 很可能必须以同样的方式重新设计...

【讨论】:

  • 不幸的是,这并没有改变任何东西。视频仍然会随机停止(每隔几次尝试一次),并且视频会彼此领先。
  • 您是否对所有并发函数/方法进行了更改?
  • 是的,当然。我也听了其他的cmet,现在视频不断停止。我想我要重写整个部分。
【解决方案3】:

您认为您锁定了互斥体并且没有其他线程可以访问它,但是两个线程看起来都更改了布尔值并尝试锁定互斥体。不是吗?

布尔值已设置,但在互斥锁暂时未锁定时访问它。

查看维基百科文章Double-checked locking。会有帮助的。

【讨论】:

  • 如果我的两个线程都使用相同的变量,这不适用吗?但是我的两个线程都基于一些不同的对象,每个都有自己独特的变量集
  • 尝试将其设置为 volatile 并仔细检查,仅用于您的共享标志。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-03-31
  • 2011-11-07
  • 1970-01-01
  • 2013-05-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多