线程视频播放器同步

Mic*_* IV 16 c++ qt multithreading

免责声明:几天前我在codereview上问了这个问题,但没有得到答案.在这里我将问题格式从审核请求更改为特定问题.

我正在开发一个具有以下设计的视频播放器:

主线程 - 是GUI线程(Qt SDK).

第二个线程 - 玩家线程接受来自GUI线程的命令来播放,前进,后退,停止等等.现在,该线程以恒定循环运行,并使用互斥锁和等待条件与主线程命令同步.

我有这个代码的2个问题:

我不觉得我的设计是完全正确的:我正在使用互斥锁和原子变量.我想知道我是否只能保留原子并仅使用锁来设置等待条件.

我正在遇到不一致的错误(可能是由于当播放命令试图锁定已经被播放循环工作的线程锁定的互斥锁时的条件竞争)当我运行"play"命令激活线程循环内的循环时.所以我想它阻止了对主线程的共享变量的访问.

我已经从不需要的东西中删除了代码,它通常是这样的:

  void PlayerThread::drawThread()//thread method passed into new boost::thread
{

   //some init goes here....

      while(true)
      {
          boost::unique_lock<boost::mutex> lock(m_mutex);
          m_event.wait(lock); //wait for event

          if(!m_threadRun){
             break; //exit the tread
          }

           ///if we are in playback mode,play in a loop till interrupted:
          if(m_isPlayMode == true){

              while(m_frameIndex < m_totalFrames && m_isPlayMode){

                       //play
                       m_frameIndex ++;

              }

               m_isPlayMode = false;

          }else{//we are in a single frame play mode:

               if(m_cleanMode){ ///just clear the screen with a color

                       //clear the screen from the last frame
                       //wait for the new movie to get loaded:

                       m_event.wait(lock); 


                       //load new movie......

               }else{ //render a single frame:

                       //play single frame....

               }


          }


      }

}
Run Code Online (Sandbox Code Playgroud)

以下是上述类的成员函数,它们向线程循环发送命令:

void PlayerThread::PlayForwardSlot(){
//   boost::unique_lock<boost::mutex> lock(m_mutex);
    if(m_cleanMode)return;
    m_isPlayMode = false;
    m_frameIndex++;
     m_event.notify_one();
}

 void PlayerThread::PlayBackwardSlot(){
 //  boost::unique_lock<boost::mutex> lock(m_mutex);
  if(m_cleanMode)return;
   m_isPlayMode = false;
   m_frameIndex-- ;
   if(m_frameIndex < 0){
       m_frameIndex = 0;
   }

    m_event.notify_one();

 }


 void PlayerThread::PlaySlot(){
 // boost::unique_lock<boost::mutex> lock(m_mutex);
   if(m_cleanMode)return;
   m_isPlayMode = true;
   m_event.notify_one(); //tell thread to  start playing.

  }
Run Code Online (Sandbox Code Playgroud)

m_cleanMode,m_isPlayModem_frameIndex这样的所有标志成员都是原子的:

  std::atomic<int32_t>   m_frameIndex;
  std::atomic<bool>      m_isPlayMode; 
  std::atomic<bool>      m_cleanMode;
Run Code Online (Sandbox Code Playgroud)

问题摘要::

  1. 使用原子时我需要互斥锁吗?

  2. 我是否在线程的while循环内的正确位置设置等待?

  3. 有没有更好的设计建议?

更新:

虽然我得到了一个似乎正确方向的答案但我并不理解它.特别是伪代码部分正在谈论服务.我完全不清楚它是如何工作的.我想得到一个更精心的答案.我也很奇怪,我只收到了一个这样一个常见问题的建设性答案.所以我重置了赏金.

UmN*_*obe 6

您的代码最大的问题是您无条件地等待.boost :: condition :: notify_one 唤醒正在等待的线程.这意味着Forward Step\Backward Step然后Play,如果速度不够快会忽略播放命令.我不明白clean mode,但你至少需要

if(!m_isPlayMode)
{
     m_event.wait(lock);
}
Run Code Online (Sandbox Code Playgroud)

在你的代码中停止并踩到一个框架几乎是一样的.你可能想使用三态PLAY,STEP, STOP来使用推荐的等待条件变量的方式

while(state == STOP)
{
    m_event.wait(lock);
}
Run Code Online (Sandbox Code Playgroud)

1.使用原子时我需要互斥锁吗?

技术上是的.在这个具体案例中,我不这么认为.目前的比赛条件(我注意到):

  • 播放模式,前进播放和播放不会导致相同,m_frameIndex取决于是否drawThreadwhile(m_frameIndex < m_totalFrames && m_isPlayMode)循环内.确实m_frameIndex可以增加一次或两次(前进).
  • PlaySlot如果在接收下一个事件之前drawThread执行,则可以忽略进入播放状态m_isPlayMode = false;.现在这是一个非问题,因为它只会在m_frameIndex < m_totalFrames假的情况下发生.如果PlaySlot正在修改m_frameIndex那么你将有推动游戏的情况,没有任何事情发生.

2.我是否在线程的while循环内的正确位置设置等待?

为简单起见,我建议在代码中只有一次等待.并明确说明使用特定命令要做的下一件事:

 PLAY, STOP, LOADMOVIE, STEP
Run Code Online (Sandbox Code Playgroud)

3.有关更好设计的建议吗?

使用显式事件队列.您可以使用基于Qt(需要Qthreads)或基于增强的功能.基于提升的一个使用a boost::asio::io_service和a boost::thread.

您使用以下命令启动事件循环:

boost::asio::io_service service;
//permanent work so io_service::exec doesnt terminate immediately.
boost::asio::io_service::work work(service); 
boost::thread thread(boost::bind(&boost::asio::io_service::exec, boost::ref(service)));
Run Code Online (Sandbox Code Playgroud)

然后使用GUI从GUI发送命令

MYSTATE state;
service.post(boost::bind(&MyObject::changeState,this, state));
Run Code Online (Sandbox Code Playgroud)
  • 考虑到状态没有改变,你的游戏方法应该请求另一个游戏,而不是循环.它允许更好的用户抢占.
  • 您的步骤方法应在显示框架之前请求停止.

伪代码:

play()
{
 if(state != PLAYING)
   return;
 drawframe(index);
 index++;
 service.post(boost::bind(&MyObject::play, this));
}

stepforward()
{
 stop();
 index++;
 drawframe(index);
}

stepbackward()
{
 stop();
 index--;
 drawframe(index);
}
Run Code Online (Sandbox Code Playgroud)

编辑:只有一名选手线程被创建一次,并且只执行一个事件循环.相当于QThread :: start().只要循环没有返回,线程就会存在,直到work 对象被销毁或显式停止服务为止.当您请求停止服务时,所有尚未处理的已发布任务将首先执行.如有必要,您可以中断线程以便快速退出.

当有一个动作调用时,你在玩家线程运行的事件循环中发布.

注意:您可能需要共享服务和线程的指针.您还需要在播放方法中放置中断点,以便在播放期间干净地停止线程.你不需要像以前那么多的原子.您不再需要条件变量.


Jan*_*net 1

有更好的设计建议吗?

是的!由于您使用的是 Qt,我强烈建议使用 Qt 的事件循环(除了 UI 的东西,这是我认为该库的主要卖点之一)和异步信号/槽来进行控制,而不是您自己开发的同步,这 - 作为你发现——这是一项非常脆弱的事业。

这将为您当前的设计带来的主要变化是,您必须将视频逻辑作为 Qt 事件循环的一部分,或者更简单,只需执行QEventLoop::processEvents. 为此,您将需要一个QThread. 然后就非常简单了:您创建一些继承自的类,QObject假设PlayerController该类应包含play,等信号pause,以及一个具有插槽, , (或不带 on ,您的偏好)的stop类。然后在 GUI 线程中创建该类的“控制器”对象,并在“视频”线程中创建该对象(或使用)。这很重要,因为 Qt 具有线程关联的概念来确定在哪个线程 SLOT 中执行。不通过执行 来连接对象。现在从 GUI 线程对“控制器”的任何调用都将导致“播放器”的方法在下一个事件循环迭代时在视频线程中执行。然后您可以在其中更改布尔状态变量或执行其他类型的操作,而无需显式同步,因为您的变量现在仅是来自视频线程的更改。PlayeronPlayonPauseonStopPlayerControllerPlayerQObject::moveToThreadQObject::connect(controller, SIGNAL(play()), player, SLOT(onPlay()))PlayerController:playonPlay

所以沿着这些思路:

class PlayerController: public QObject {
Q_OBJECT

signals:
    void play();
    void pause();
    void stop();
}

class Player: public QObject {
Q_OBJECT

public slots:
    void play() { m_isPlayMode = true; }
    void pause() { m_isPlayMode = false; }
    void stop() { m_isStop = true; };

private:
    bool m_isPlayMode;
    bool m_isStop;
}

class VideoThread: public QThread {

public:
    VideoThread (PlayerController* controller) {
        m_controller = controller;
    }

protected:
    /* override the run method, normally not adviced but we want our special eventloop */
    void run() {
        QEventLoop loop;
        Player* player = new Player;

        QObject::connect(m_controller, SIGNAL(play()), player, SLOT(play()));
        QObject::connect(m_controller, SIGNAL(pause()), player, SLOT(pause()));
        QObject::connect(m_controller, SIGNAL(stop()), player, SLOT(stop()));

        m_isStop = false;
        m_isPlayMode = false;
        while(!m_isStop) {
            // DO video related stuff
            loop.processEvents();
        }
    }


private:
    PlayerController* m_controller;
}



// somewhere in main thread
PlayerController* controller = new PlayerController();
VideoThread* videoThread = new VideoThread(controller);
videoThread.start();
controller.play();
Run Code Online (Sandbox Code Playgroud)