std::thread : how to declare a thread in class bod

2020-06-26 08:01发布

I want to have a thread for each instance of Page object. At a time only one of them can execute (simply checks if pointer to current running thread is joinable or not..)

class Page : public std::vector<Step>
{
    // ....
    void play();
    void start(); // check if no other thread is running. if there is a running thread, return. else join starter
    std::thread starter; // std::thread running this->play()
    static std::thread* current; // pointer to current running thread
    // ...
};

I want to be able to fire-up starter threads of Page objects. for example like this:

Page x , y , z;
// do some stuff for initialize pages..
x.start();
// do some other work
y.start(); // if x is finished, start y otherwise do nothing
// do some other lengthy work
z.start(); // if x and y are not running, start z

I can't manage to declare started as a member of Page. I found that it's because of the fact std::threads can only initialized at declaration time. (or something like that, cause it's not possible to copy a thread)

void x()
{
}
//...
std::thread t(x);          // this is ok
std::thread r;             // this is wrong, but I need this !
r = std::thread(this->y);  // no hope
r = std::thread(y);        // this is wrong too

2条回答
戒情不戒烟
2楼-- · 2020-06-26 08:15

You can initialize the thread to the function to run by using a member initializer list. For example, consider this constructor for Page:

class Page {
public:
    Page(); // For example

private:
    std::thread toRun;
};

Page::Page() : toRun(/* function to run */) {
    /* ... */
}

Notice how we use the initialization list inside the Page constructor to initialize toRun to the function that ought to be run. This way, toRun is initialized as if you had declared it as a local variable

std::string toRun(/* function to run */);

That said, there are two major problems I think that you must address in your code. First, you should not inherit from std::vector or any of the standard collections classes. Those classes don't have their destructors marked virtual, which means that you can easily invoke undefined behavior if you try to treat your Page as a std::vector. Instead, consider making Page hold a std::vector as a direct subobject. Also, you should not expose the std::thread member of the class. Data members should, as a general rule, be private to increase encapsulation, make it easier to modify the class in the future, and prevent people from breaking all of your class's invariants.

Hope this helps!

查看更多
别忘想泡老子
3楼-- · 2020-06-26 08:32

Never publicly inherit from a std container, unless the code is meant to be throw away code. An honestly it's terrifying how often throw away code becomes production code when push comes to shove.

I understand you don't want to reproduce the whole std::vector interface. That is tedious write, a pain to maintain, and honestly could create bugs.

Try this instead

class Page: private std::vector
{
public:
  using std::vector::push_back;
  using std::vector::size;
  // ...
};

Ignoring the std::vector issue this should work for the concurrency part of the problem.

class Page
{
  ~Page( void )
  {
    m_thread.join();
  }

  void start( void );

private:

  // note this is private, it must be to maintain the s_running invariant
  void play( void )
  {
    assert( s_current == this );

    // Only one Page at a time will execute this code.

    std::lock_guard<std::mutex> _{ s_mutex };
    s_running = nullptr;
  }

  std::thread m_thread;

  static Page* s_running;
  static std::mutex s_mutex;
};

Page* Page::s_running = nullptr;
std::mutex Page::s_mutex;
std::condition Page::s_condition;

void Page::start( void )
{
  std::lock_guard<std::mutex> _{ s_mutex };

  if( s_running == nullptr )
  {
    s_running = this;
    m_thread = std::thread{ [this](){ this->play(); } };
  }
}

This solution is may have initialization order issues if Page is instantiate before main()

查看更多
登录 后发表回答