0

I have written a basic server in C++ that runs in an infinite while loop. It receives signals from a client to do things. The main process that I want is to initiate or stop some tracking software that I have written.

I would like the server to still be able to receive signals while the tracking software is being run (e.g. if a stop signal was given). I figured that the best way to do this would be to create a separate thread for the tracking software, so that is what I did:

void Server::tracking(Command c)
{
  //I have since changed this method. The new implementation is below
  //switch(c) {
  //  case START:
  //    player = VideoPlayer();
  //    player.setTrackStatus(true);
  //    t = std::thread(&Server::track, this);
  //    t.detach();
  //    break;
  //  case STOP:
  //    player.setTrackStatus(false);
  //    break;
  //  default:
  //    break;
  //}
} 

Server::track just calls player.run()

VideoPlayer is the class that contains the main tracking loop. The track status is what determines whether or not the tracking loop continues to execute.

This works fine the first time I run it, it is able to start the tracking and stop it. The problem arises when I try to send another "START" signal without restarting the server.

I have narrowed down the problem to the cv::namedWindow function. Here is the start of the VideoPlayer class:

void VideoPlayer::run(void)
{
  //I have since changed this method. The new implementation is below
  //initVC();
  //openStream();
}

initVC() is where I create the namedWindow and openStream contains the main tracking loop. Here is initVC (which is where I believe the problem lies):

void VideoPlayer::initVC()
{
  if(!capture.open("cut.mp4")) {
      throw "Cannot open video stream";
  }
  std::cout << "flag 1" << std::endl;
  cv::namedWindow("Tracker", CV_WINDOW_AUTOSIZE);
  std::cout << "flag 2" << std::endl;
}

I have found that on the second run (i.e. tracking has been started and stopped and the server has not been closed and reopened), that flag 2 never gets run. I also found that, if I omit namedWindow then the program stops before imshow(). It might also be worth noting that the program doesn't crash, it just seems to pause.

I have a feeling that I am doing something wrong with the threading, because I have never used threads in C++ before.

Thanks!

EDIT: I have been attempting to add some of the changes suggested by @Dom, however I am still having a similar issue to before. I will post some additional code below with comments to try to explain.

Server::tracking:

This is meant to initiate tracking based on the command received from the client.

void Server::tracking(Command c)
{
  switch(c) {
    case START:
      if(!isRunning) {
        player = make_unique<VideoPlayer>();
        isRunning = true;
        player->setTrackStatus(isRunning);
      }
      else {
        std::lock_guard<std::mutex> lock(mtx);
      }
      break;
    case STOP:
      if(isRunning) {
        player->terminate();
        player->exit(); //Destroys OpenCV stuff
        player->joinThread();
        player = nullptr;
        isRunning = false;
      }
      else {
        std::lock_guard<std::mutex> lock(mtx);
      }
      break;
    default:
      break;
  }
}

VideoPlayer Constructor:

VideoPlayer::VideoPlayer () : trackStatus(true)
{
  tracker = Tracker(); //A separate class, related to the data from the tracked 
                       //object. Not relevant to the current question
  track_t = std::thread(&VideoPlayer::run, this);
  return;
}

VideoPlayer::run:

void VideoPlayer::run(void)
{
  std::lock_guard<std::mutex> lock(mtx);
  initVC(); //Initialises the OpenCV VideoCapture
  openStream(); //Contains the main tracking code
  return;
}

VideoPlayer::openStream:

void VideoPlayer::openStream() 
{
  while(trackStatus) {
  ... //tracking stuff
  }
  return;
}

VideoPlayer::terminate:

void VideoPlayer::terminate()
{
  track = false;
  std::lock_guard<std::mutex> lock(mtx);
}

VideoPlayer::joinThread:

void VideoPlayer::joinThread()
{
  if(track_t.joinable()) {
    std::cout << "flag 1" << std::endl;
    track_t.join();
    std::cout << "flag 2" << std::endl; //It fails here on my second "run"
    return;
  }
}

Basically, my program stops just before the track_t.join(), the second time I run the tracking (without restarting the server). flag 1 and flag 2 print the first time that I run the tracking. All of the OpenCV components appear to have been disposed of correctly. If I then try to open the tracking again, firstly, the tracking doesn't seem to start (but the program doesn't crash), and then if I try to stop the tracking, it prints flag 1 but then stops indefinitely without printing flag 2

Sorry for the lengthy post. I hope this gives a bit more context to what I'm trying to achieve

liamw9
  • 527
  • 2
  • 6
  • 19
  • It is absolutely not clear to me, what the code should be doing as important definitions of methods are skipped (for instance Server::track)... however it seems, you are not communicating with the thread, you just detache it ant that's it - it seems, that player.setTrackStatus does nothing at all, it just changes a member value of the player. First of all, I would make some basics on one thread - main thread - and later extend the application to be multithreaded, before try to study for example https://www.tutorialcup.com/cplusplus/multithreading.htm – Dom Aug 13 '16 at 11:46
  • Sorry, I forgot to mention that function. `Server::track` just calls `player.run()`. I wasn't sure if the function passed into the new thread could be in another class. The `setTrackStatus` function changes a boolean in `player`. This boolean determines whether the main tracking loop executes (i.e. `while(tracking) { ... }`). Thanks, I'll give it a go without the thread and then try to reintroduce it – liamw9 Aug 13 '16 at 11:52
  • I have tried running it without threads, and the problem that I was experiencing has been solved. However, this now means that my server cannot accept any incoming signals until the tracking software is complete. I have read the tutorial that you recommended. I understand that you can use `join()`, however, I don't see how this would allow me to run processes concurrently, as the main thread has to wait for it to complete before continuing. Thanks for your help! – liamw9 Aug 13 '16 at 13:30
  • Great, could you describe your desire in more details? Because it seems, it is not so trivial... What should be done concurrently? Just to start one track and then stop the track, or play more tracks simultaneously? (btw. I am not sure, we are on the right place to have long discussions...) Cold you edit your question and add "Server::track just calls player.run()."? – Dom Aug 13 '16 at 21:31
  • Only one instance of the tracking software will be run at once. I need the loop that runs the server to be running at the same time as the loop that runs the tracking (so that the server can still receive requests, such as a signal to stop the tracking). Sure, I'll edit that now – liamw9 Aug 14 '16 at 00:01

1 Answers1

1

So your tracking app. could be implemented as follows:

#include <chrono>
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <memory>
#include <atomic>

enum Command : char
{
    START = '1',
    STOP = '0'
};

static std::mutex mtx; // mutex for I/O stream

class VideoPlayer
{
public:
    VideoPlayer() : trackStatus()
    {
        initVC();
        openStream();
    };

    ~VideoPlayer()
    {
        closeStream();
        uninitVC();
    }

    void setTrackStatus(bool status)
    {
        if (status && trackStatus == false)
        {
            trackStatus = status;
            t = std::thread(&VideoPlayer::run, this);
        }
        else
        {
            trackStatus = false;
            if (t.joinable())
            {
                t.join();
            }
        }
    }


private:
    void run()
    {
        tId = std::this_thread::get_id();
        {
            std::lock_guard<std::mutex> lock(mtx);
            std::cout << "run thread: " << tId << std::endl;
        }

        while (trackStatus)
        {
            {
                std::lock_guard<std::mutex> lock(mtx);
                std::cout << "...running thread: " << tId << std::endl;
            }
            std::this_thread::sleep_for(std::chrono::seconds(1)); // encode chunk of stream and play, whatever....
        }
    }

    void initVC()
    {
        /*
        if (!capture.open("cut.mp4"))
        {
            throw "Cannot open video stream"; --> http://stackoverflow.com/questions/233127/how-can-i-propagate-exceptions-between-threads
        }
        std::cout << "flag 1" << std::endl;
        //cv::namedWindow("Tracker", CV_WINDOW_AUTOSIZE);
        //std::cout << "flag 2" << std::endl;
        */
    }

    void uninitVC()
    {
    }

    void openStream()
    {
    }

    void closeStream()
    {
    }

private:
    std::atomic<bool> trackStatus; // atomic, because of access from another (main) thread
    std::thread t;  // thread for "tracking"
    std::thread::id tId; // ID of the "tracking" thread
};


class Server
{
public:
    Server() : isRunning(), player(std::make_unique<VideoPlayer>())
    {
    }

    ~Server() = default;

    void tracking(Command c)
    {
        switch (c)
        {
            case START:
                if (!isRunning)
                {
                    isRunning = true;
                    player->setTrackStatus(isRunning);
                }
                else
                {
                    std::lock_guard<std::mutex> lock(mtx);
                    std::cout << "Player is already running...\n";
                }
            break;
            case STOP:
                if (isRunning)
                {
                    player->setTrackStatus(!isRunning);
                    isRunning = false;
                }
                else
                {
                    std::lock_guard<std::mutex> lock(mtx);
                    std::cout << "Player is not running...\n";
                }
            break;
            default:
            break;
        }
    }

private:
    std::unique_ptr<VideoPlayer> player;
    bool isRunning;
};

int main()
{
    std::cout << "main thread: " << std::this_thread::get_id() << std::endl;

    Server srv;

    char cmd = -1;
    while (std::cin >> cmd)
    {
        switch (cmd)
        {
            case Command::START:
            {
                srv.tracking(Command::START);
            }
            break;
            case Command::STOP:
            {
                srv.tracking(Command::STOP);
            }
            break;
            default:
                std::cout << "Unknown command...\n";
            break;
        }
    }
}

You can move creation of the thread to constructor of VideoPlayer and join in destructor (I would prefer it...):

    VideoPlayer() : trackStatus(true)
    {
        initVC();
        openStream();
        t = std::thread(&VideoPlayer::run, this);
    };

    ~VideoPlayer()
    {
        closeStream();
        uninitVC();
        if (t.joinable())
        {
            t.join();
        }
    }

but some modifications are needed to terminate and clean the thread, you can use something like

public:
    void VideoPlayer::terminate()
    {
        {
            std::lock_guard<std::mutex> lock(mtx);
            std::cout << "terminate thread: " << tId << std::endl;
        }
        trackStatus = false;
    }

however, than is needed to create instance of player during START

player = std::make_unique<VideoPlayer>();

and then Terminate() and delete the player during STOP

player->terminate();
player = nullptr;

Hope, this inspired you enough ;-)

Dom
  • 532
  • 1
  • 9
  • 23
  • Thanks for taking the time to answer this. This is something that I wouldn't have come up with independently. I'll try what you're suggesting and get back to you – liamw9 Aug 14 '16 at 08:13
  • Thanks again for the help. Unfortunately, I still haven't been able to solve the issue. I tried to incorporate your suggestions. I will update my post to include more of my code. If you are able to help then that would be great. I will also add comments to try to explain – liamw9 Aug 15 '16 at 11:38
  • Welcome :-) I gave you a full example (you can simulate to start / stop the thread by pushing keys 1 and 0). Dou you understand each line of the code? If not or you have any doubts, go back and examine it line by line, study meaning of std::atomic, mutexes, threads... what RAII is, than you will be able to rearrange the code as you need... It takes some time, but it's worth it :-) I am afraid, I can't give you a better advice, sorry for that. – Dom Aug 15 '16 at 23:34
  • Thanks. I think I just need to get to grips with some of the concepts introduced in your example – liamw9 Aug 15 '16 at 23:36
  • I just thought I would quickly update you. I found that the issue was to to with the imshow and namedWindow functions and multi-threading. I was using the imshow while developing, however it's only the data that I require., so by commenting these function calls out, I found that I could start and restart my program with the tracking functions being called correctly – liamw9 Aug 16 '16 at 13:18